Commit Graph
493 Commits
Author SHA1 Message Date
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 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 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
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 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
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 774a639e9a . 2026-04-12 14:16:17 +02:00
maziggy c4ebe5a70c ● fix(spoolbuddy): actually power off HDMI on idle instead of CSS overlay (#937)
The SpoolBuddy kiosk's "screen blank timeout" setting only painted a
  black CSS overlay over the browser window — the HDMI panel's backlight
  stayed on indefinitely, wasting power and risking burn-in on
  OLED/LED panels.

  Move blanking down to the OS layer:

  - install.sh now installs swayidle + wlopm + jq and rewrites labwc's
    autostart to launch a new spoolbuddy-idle.sh watchdog instead of the
    old `wlr-randr --on` keep-alive loop.
  - The watchdog sources /opt/bambuddy/spoolbuddy/.env, derives device_id
    from the first non-loopback MAC (same algorithm as daemon/config.py),
    fetches the configured blank_timeout from the backend once on boot,
    and execs `swayidle -w timeout $T 'wlopm --off HDMI-A-1' resume
    'wlopm --on HDMI-A-1'`. Touch/keypress wakes via labwc's input event
    path. timeout=0 skips swayidle entirely so existing installs that
    never picked a timeout keep their current always-on behavior.
  - New GET /api/v1/spoolbuddy/devices/{id}/display endpoint returns the
    current brightness + blank_timeout. Gated on INVENTORY_UPDATE (same
    level the daemon heartbeat key already uses) so existing SpoolBuddy
    API keys work without extra permissions.
  - SpoolBuddyLayout drops blanked state, the blank timer, activity
    listeners, resetActivity, and the CSS overlay. Runtime updates to
    the timeout take effect on next kiosk/browser restart; default for
    newly-enabled blanking is 300 seconds.
2026-04-11 12:42:49 +02:00
maziggy 99c193b535 refactor(colors): color_catalog is the single source of truth (#857)
The Printer tab AMS popup and spool auto-provisioner resolved color
  names from hardcoded tray_id_name tables with a suffix-code fallback —
  and suffix codes like "R1" are not globally unique across material
  families. A17-R1 (PLA Translucent Cherry Pink) fell through the
  fallback and resolved to "Scarlet Red" (A01-R1, PLA Matte), baking
  the wrong name into auto-created inventory spools.

  The fix removes the hardcoded tables entirely. Backend resolves color
  names via the existing color_catalog table by hex; frontend fetches a
  compact {hex: name} map once per session via a new
  GET /inventory/colors/map endpoint (auth-gated but not on
  inventory:read — read-only views need it too) and stores it in a
  ColorCatalogProvider context. A useSyncExternalStore hook cascades a
  re-render into pages mounted before the fetch completes so they
  refresh from HSL-fallback names once the catalog loads.

  Existing auto-provisioned spools keep their stored names; only new
  provisioning and live display benefit. Co-Authored-By is intentionally
  omitted here per project convention — set it via git config if needed.
2026-04-11 12:10:45 +02:00
maziggy 8266d225d2 fix(energy): date-range energy in total mode + restart-resilient per-print tracking (#941)
The Statistics page reported "Gesamt" (All Time) kWh correctly but showed
  zero for Today/Week/Month in total-consumption mode. Two bugs drove it:

  1. The starting plug counter was kept in an in-memory dict
     `_print_energy_start` that was lost on any backend restart mid-print, so
     the per-print `energy_kwh` delta silently never got computed. The stats
     endpoint's fallback path `SUM(PrintArchive.energy_kwh)` therefore summed
     to zero for users running in total mode.
  2. Total-consumption mode has no per-print delta by design — it includes
     idle/preheat/standby — so the fallback to archive rows was the wrong
     strategy even when the data existed.

  Fix, in two parts:

  - Persist `energy_start_kwh` on the archive row and read it back from a
    fresh session at print end. Deletes `_print_energy_start` and its 5
    call sites, replacing them with a single `_record_energy_start()` helper.
    Per-print tracking is now restart-resilient regardless of tracking mode.
  - Add hourly `smart_plug_energy_snapshots` table + `_snapshot_loop()` in
    SmartPlugManager. Rewrote the `/archives/stats` energy branch as
    `_sum_snapshot_deltas()` which computes per-plug
    `max(0, last-in-range - baseline)` where baseline is the latest snapshot
    at or before the range start, falling back to the earliest-ever snapshot
    and signalling `energy_data_warming_up` when no pre-range baseline
    exists (fresh upgrade). MQTT plugs are skipped from snapshots since they
    only report "today" and have no lifetime counter.

  Frontend: QuickStatsWidget renders an AlertTriangle next to Energy Used /
  Energy Cost with a tooltip when `energy_data_warming_up` is true, so the
  "low values right after upgrading" situation is explained in-product.
  Fully localised across 7 UI languages.

  Tests: new backend unit tests cover the snapshot delta arithmetic
  (baseline/endpoint, counter reset clamp, multi-plug, warming-up fallback,
  endpoint windowing), per-print restart resilience via expunge_all, and the
  snapshot task lifecycle (start idempotent, stop cancels). Frontend tests
  assert the warning icon appears only when the flag is set and only on the
  energy tiles.

  Docs: updated `CHANGELOG.md`, `README.md`, wiki `features/energy.md`,
  wiki `features/statistics.md`, and website `features.html` with the new
  behaviour and warming-up explanation.
2026-04-11 11:14:54 +02:00
maziggy b5c8c2cdf5 Add SpoolBuddy device management settings tab
Previously, if a SpoolBuddy daemon crashed during registration it could
  end up registered twice. The kiosk UI silently used only the first
  device and there was no UI path to remove the orphan — administrators
  had to delete the row directly in the database.

  Adds a new Settings → SpoolBuddy tab that lists every registered device
  with live connection status, system details (firmware, IP, CPU temp,
  memory, disk, OS, daemon + system uptime), hardware health flags, and
  an Unregister action gated by a confirm modal. A yellow banner appears
  whenever more than one device is registered to flag likely crash-
  duplicates. Backend adds DELETE /spoolbuddy/devices/{device_id} gated
  by inventory:delete and broadcasts spoolbuddy_unregistered over WS so
  other tabs refresh immediately.

  The tab header shows a device-count pill and a green/gray status bullet
  reflecting whether at least one registered device is online. An online
  device that is accidentally unregistered re-registers itself on its
  next heartbeat. Localized in English, German, and Japanese. The kiosk
  layout still uses devices[0] — once the orphan is unregistered, the
  remaining device naturally becomes [0].
2026-04-11 10:22:59 +02:00
lietschaend f95b2acd7d Feature: print files directly from project view (closes #930) (#932)
* feat: print files directly from project view (closes #930)

Show printable files from linked library folders directly in the project
detail page, with Print Now and Add to Queue buttons per file. Removes
the detour through the File Manager for common reprint workflows.
2026-04-10 12:54:27 +02:00
maziggy 39a5840f67 Fix camera reconnect counter off-by-one and ffmpeg log flood (#925)
Two bugs surfaced while investigating camera reconnect behaviour in #925.

  The camera page briefly displayed "Reconnecting attempt 6 of 5" before
  giving up, because the attempt counter could be incremented to the
  maximum while the reconnect banner was still rendering. The displayed
  value is now clamped to the configured maximum.

  Every failed ffmpeg spawn logged the full ~20-line ffmpeg version,
  configuration, and lib* banner, producing hundreds of lines of noise
  per failed camera click (one reported click produced 555 log lines
  across 30 retries). A new _summarize_ffmpeg_stderr helper strips the
  banner and caps output at the last 10 meaningful lines, applied at
  all three stderr log sites (immediate-failure, stream-ended,
  read-timeout). Covered by unit tests for empty input, banner
  stripping, line cap, blank-line filtering, and banner-only input.

  The underlying "camera service stops accepting connections after
  prolonged uptime" behaviour in the X1C firmware is still under
  investigation — these two fixes are independent of that root cause.
2026-04-09 11:29:57 +02:00
maziggy 848f558105 LDAP: POSIX primary group support and default fallback group
Two related LDAP authentication changes.

  Fix: POSIX primary group membership was ignored. authenticate_ldap_user
  only searched for posixGroup entries via memberUid (supplementary
  groups). A user's primary group — referenced by the gidNumber attribute
  on the user object matching gidNumber on a posixGroup — was never
  resolved, so users whose role came from their primary group landed
  without the expected permissions. The authenticator now runs a second
  search for posixGroup entries whose gidNumber matches the user's
  primary gidNumber, then dedupes DNs case-insensitively before passing
  the list to resolve_group_mapping (LDAP DNs are case-insensitive by
  spec).

  New feature: ldap_default_group setting. Settings → Authentication →
  LDAP → Advanced has a new "Default group" selector. When an LDAP user
  authenticates but is not listed in any mapped LDAP group, they are
  assigned to this fallback group instead of being left with no groups
  (and therefore no permissions). A warning is logged each time the
  fallback is applied so admins can spot missing group assignments.
  Empty setting preserves the old behavior.

  Tests: added 4 mocked authenticate_ldap_user tests covering primary
  gidNumber lookup, dedupe of overlapping memberUid+primary gid matches,
  case-insensitive DN dedupe, and the guard when a user entry has no
  gidNumber attribute. Also extended the existing parse_ldap_config tests
  to cover the new default_group field.

  Backend: ldap_service.py (primary group + dedupe + default_group
  field), schemas/settings.py (schema field), api/routes/auth.py
  (fallback wiring in _provision_ldap_user / _sync_ldap_user).

  Frontend: LDAPSettings.tsx default-group dropdown in the Advanced
  collapsible, api/client.ts type field, new i18n keys in all 7 locales
  (defaultGroup, defaultGroupNone, defaultGroupHint).
2026-04-09 10:48:42 +02:00
maziggy d0b91ad4d5 Fix support bundle leaking virtual printer IP address
The debug support bundle included virtual_printer_remote_interface_ip
  unmasked in support-info.json. The setting key didn't match any
  sensitive-key filter substring. Added "_ip" to the filter set so IP
  address settings are excluded. Log file content was already redacted
  by the existing IPv4 regex.
2026-04-09 08:25:40 +02:00
maziggy 813d9dde38 Fix Spoolman location not cleared on auto-sync when spool removed from AMS (#921)
The on_ams_change auto-sync callback set locations for new spools but
  never called clear_location_for_removed_spools(), leaving stale locations
  that caused double-booked slots. Also pass synced_spool_ids in the
  single-printer sync route to match the sync-all endpoint behavior.
2026-04-09 08:16:54 +02:00
maziggy b76d6210cf Add SpoolBuddy quick menu with power control and system commands (#893)
Swipe down from the top of the SpoolBuddy display to open a quick-access
  menu for toggling printer smart plugs and managing the device (restart
  daemon, restart browser, reboot, shutdown). All destructive actions
  require confirmation.

  Backend: new POST /spoolbuddy/devices/{id}/system/command endpoint
  queuing reboot/shutdown/restart_daemon/restart_browser commands.
  Daemon: handles commands via subprocess (sudo reboot, systemctl restart).
  Frontend: SpoolBuddyQuickMenu component, swipe-down gesture detection,
  i18n keys for all 7 locales.
2026-04-08 11:29:44 +02:00
maziggy b6599dd419 Add LDAP/Active Directory authentication (#794)
Users can authenticate against an LDAP/AD server with configurable
  server URL, bind DN, search base, and user filter. Supports StartTLS
  and LDAPS — plaintext is not allowed. Both Active Directory (memberOf)
  and POSIX groups (memberUid) are mapped to BamBuddy groups on each
  login. Auto-provisioning creates local accounts on first LDAP login.
  Local admin accounts remain as fallback when LDAP is unreachable.
  Password management is disabled for LDAP users.
2026-04-08 10:41:27 +02:00
maziggy 4c052e04e1 Fix SpoolBuddy inventory not updating on spool changes (#905)
Spool CRUD endpoints (create, bulk create, update, delete, archive,
  restore) did not emit websocket events, so SpoolBuddy Dashboard and
  other tabs relying on event-driven cache invalidation never refreshed.

  All inventory mutation endpoints now broadcast an `inventory_changed`
  websocket event. Frontend handles it by invalidating `inventory-spools`.
2026-04-07 12:32:44 +02:00
maziggy f006472f79 Add auto-print G-code injection for queue items (#422)
Per-model start/end G-code snippets configurable in Settings (Workflow
  tab). Queue items get "Inject G-code" toggle — scheduler injects
  snippets into a temp 3MF copy before FTP upload. Supports Farmloop,
  SwapMod, AutoClear, Printflow 3D and similar bed-clearing systems.
  Original files are never modified.
2026-04-05 11:14:26 +02:00
maziggy 1645b51dad Add external folder subfolder preservation and fix file manager stale UI after delete
External folder scan now mirrors disk subfolder structure into the folder
  tree instead of flattening all files into root. Hidden directories are
  filtered, orphaned subfolders are cleaned up on rescan. Fixes #890.

  File manager delete endpoints (folder, file, bulk) now commit before
  returning the response — previously relied on post-response auto-commit,
  causing a race where the frontend refetch arrived before the commit.
2026-04-05 09:33:46 +02:00
maziggy edfd2e77c0 Revert "Add Load/Unload External filament buttons to printer controls"
This reverts commit e3cde14a0b.
2026-04-05 08:21:37 +02:00
NNeerr00 e3cde14a0b Add Load/Unload External filament buttons to printer controls
New dedicated MQTT methods, API endpoints, and UI buttons for
loading/unloading filament from the external spool holder without
requiring an AMS. Includes state guards to prevent load when filament
is already loaded and unload when nothing is loaded.
2026-04-05 00:04:26 +02:00
maziggy 2a6df22075 Restrict temp file permissions for camera snapshots
Camera snapshot, test, and plate detection endpoints created temporary
  JPEG files with default 0644 permissions. Switch from NamedTemporaryFile
  to mkstemp with explicit 0600 permissions.
2026-04-04 13:18:53 +02:00
maziggy 70b12e1949 Fix API key empty printer_ids granting full access
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.
2026-04-04 13:01:54 +02:00
maziggy 5709282200 Add authentication to bug report debug logging endpoints
The bug report endpoints (start-logging, stop-logging, submit) had no
  authentication, allowing anyone on the network to enable debug logging,
  retrieve sanitized system logs, and trigger bug report submissions when
  auth was enabled. All three now require permission when auth is enabled:
  start-logging requires settings:update, stop-logging and submit require
  settings:read. Endpoints remain open when auth is disabled (default).
2026-04-04 12:54:31 +02:00
maziggy 7fa00ed475 Fix path traversal vulnerability in file upload endpoints
Archive upload endpoints used the client-supplied filename directly
  in file paths, allowing an authenticated attacker to write files
  outside the intended directory (e.g. ../../evil.3mf bypasses the
  .3mf extension check). Added _safe_filename() helper that normalizes
  backslashes and extracts the basename before constructing paths.
2026-04-04 12:41:01 +02:00
maziggy 039db1217d Add shortest-job-first queue scheduling with starvation guard (#879)
New SJF toggle badge on the queue page. When enabled, the scheduler
  picks shorter print jobs before longer ones instead of FIFO. A
  starvation guard flags jobs that get skipped once, moving them to
  the front on the next cycle so long jobs can't be postponed indefinitely.

  - Add print_time_seconds and been_jumped columns to PrintQueueItem
  - Cache print duration from 3MF metadata at queue item creation
  - SJF query: printer_id, target_model, been_jumped DESC, print_time_seconds ASC, position
  - Mark jumped items in-memory after each print start
  - Toggle badge on queue page header with live state indicator
  - Frontend auto-sorts to match scheduler order when SJF enabled
  - Settings schema, boolean parsing, and migration (SQLite + PostgreSQL)
  - i18n badge keys for all 7 locales
  - 10 integration tests for SJF ordering and starvation logic
  - Wiki, website, README, and changelog updated
2026-04-04 10:25:17 +02:00
maziggy 0f0fac9b32 Show database engine and version on System Information page
Display the active database backend (SQLite or PostgreSQL) and its
  version in the Database section of the System Info page. SQLite queries
  sqlite_version(), PostgreSQL queries SELECT version() and
  pg_database_size(). Helps users verify which database is in use.
2026-04-04 09:05:35 +02:00
maziggy 610431d6b7 Add optional PostgreSQL database support
Bambuddy can now use an external PostgreSQL database via the
  DATABASE_URL environment variable. SQLite remains the default.
  Dialect-aware helpers handle upserts, PRAGMAs, FTS (FTS5 vs
  tsvector+GIN), backup/restore, and health checks. All migration
  blocks use savepoints to prevent Postgres transaction poisoning.
  Backups are always portable SQLite format regardless of backend.
  Cross-database restore imports SQLite backups into PostgreSQL
  with automatic boolean/datetime conversion, NOT NULL default
  filling, and FK constraint handling.
2026-04-03 11:33:29 +02:00
maziggy f4df4393be Add spool inventory and print archive backup to GitHub backup (#870)
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.
2026-04-02 09:59:00 +02:00
maziggy 3270179090 Add batch print quantity to print/schedule dialog (#342) 2026-04-01 11:55:30 +02:00
maziggy 9aa9fdc586 Add configurable default print options (#858) 2026-04-01 10:30:05 +02:00
maziggy 914adde5aa Add REST/Webhook smart plug type (#472) 2026-04-01 10:06:01 +02:00
maziggy 5a696f9fa3 ● Add prefer lowest remaining filament in auto-matching (#805)
When multiple AMS spools match the same type/color criteria, an optional
  setting now prefers the spool with the lowest remaining filament. This
  helps consume partial spools before starting new ones. Sorting applies
  to all matching paths: queue scheduler, print modal, and multi-printer
  mapping. Unknown remain values (-1) sort to end.
2026-03-31 14:14:15 +02:00
maziggy ec0ae162e8 Add per-user statistics filtering (#730)
Admins can now filter the Statistics page by user via a new
  stats:filter_by_user permission. A user dropdown appears in the stats
  header showing all users plus "No User (System)" for prints without
  attribution. The filter applies to all stats widgets, failure analysis,
  and CSV/Excel exports. Backend validates the permission on all 4 stats
  endpoints, returning 403 if the filter is used without authorization.
2026-03-31 12:13:58 +02:00
maziggy 046dbf3608 Add stagger to Print dialog, add plate-clear setting (#752)
Stagger option now available when printing directly to multiple printers,
  not just in queue mode. Prints are automatically queued with staggered
  start times using group size/interval from Settings. New "Require
  plate-clear confirmation" setting lets farm users disable per-printer
  plate confirmations so queued prints start automatically on finished
  printers.

  Also fixes settings API type parsing for require_plate_clear (boolean),
  stagger_group_size and stagger_interval_minutes (integer) — without this,
  saved values returned as strings would cause the settings toggle to
  always show enabled and trigger a permanent save loop.
2026-03-31 11:12:42 +02:00
maziggy f3b2a24fa0 Fix external folder scan 500 on 3MF files (#846)
scan_external_folder stored raw 3MF metadata containing _thumbnail_data
  bytes directly in the JSON column, causing a serialization error. Applied
  the same clean_metadata() pattern used by upload/zip extraction and
  removed the call to the non-existent parser.extract_thumbnail().
2026-03-30 08:36:39 +02:00
maziggy a077fd138d Fix native install misdetected as Docker in LXC containers
The _is_docker_environment() fallback assumed Docker when .git/ was
  absent, which is also true for native installs in Proxmox LXC
  containers. Replace the .git/ fallback with a check of
  /run/systemd/container (only matches docker/podman/oci, not lxc).
2026-03-27 14:35:08 +01:00
maziggy 3887938e8f Security: add token-based auth for all media endpoints
Camera streams, snapshots, thumbnails, timelapse videos, photos, QR
  codes, and cover images served via <img>/<video> tags were previously
  unauthenticated because browser media elements cannot send Authorization
  headers. When auth is enabled, these endpoints are now protected by a
  reusable stream token (?token=xxx) obtained from POST
  /printers/camera/stream-token (requires CAMERA_VIEW permission).
2026-03-27 12:58:08 +01:00
maziggy 77cb7158d2 Add SpoolBuddy System tab with live OS stats from Raspberry Pi
The daemon now collects CPU temp, core count, load average, memory/disk
  usage, OS info, and system uptime every heartbeat using stdlib-only reads
  from /proc and /sys. Stats are sent as a JSON blob in the heartbeat
  payload, stored in a new system_stats TEXT column, and displayed in a
  new "System" tab in SpoolBuddy Settings with color-coded usage bars.
2026-03-26 09:57:09 +01:00
maziggy 1df5130df2 Broadcast spool assignment changes via WebSocket for cross-tab updates
Assigning or unassigning a spool now broadcasts a spool_assignment_changed
  event to all connected WebSocket clients. The frontend handles this event
  by invalidating the spool-assignments and slotPresets caches, so other
  open tabs update automatically without a page reload.
2026-03-26 09:34:46 +01:00
maziggy 95c703282d Fix missing image thumbnails in external folder scan (#124)
External folder scan generated thumbnails for 3mf/stl/gcode files but
  skipped image files. Added IMAGE_EXTENSIONS check with
  create_image_thumbnail() to the scan loop.
2026-03-24 16:32:45 +01:00
maziggy c9efa4b8bb Fix stale AMS slot data and empty slot button visibility (#784)
Some printers (e.g. H2D) only send {id, state} in incremental MQTT
  tray updates — no tray_type, tray_color, or other fields. When
  filament is unloaded (state changes from 11 to 10), the old tray
  data persisted indefinitely because the merge logic only updates
  fields present in the incoming message.

  Backend:
  - Detect tray state != 11 without tray_type in incremental updates
    and clear stale tray data (bambu_mqtt.py)
  - Expose tray `state` field via REST API and WebSocket broadcasts
    (printer.py schema, printers.py route, printer_manager.py)
  - Include AMS tray content in WebSocket dedup key so load/unload
    transitions trigger broadcasts (main.py)

  Frontend:
  - Add `state` to AMSTray interface (client.ts)
  - Show configure/assign buttons for state=10 (spool present, not
    loaded) but hide for state=9 (truly empty) on AMS/HT slots
  - Hide fill level bar on empty slots

  Tests:
  - 5 new tests covering state-based clearing, preservation, reload,
    and idempotency
2026-03-24 16:03:34 +01:00