|
@@ -17,6 +17,7 @@ All notable changes to Bambuddy will be documented in this file.
|
|
|
- **Settings page: permission-gated instead of admin-only** — the Settings sidebar entry has always been visible to any user holding `settings:read`, but the route guard required admin role, so a non-admin with `settings:read` would see the entry, click it, and get silently redirected back to the dashboard. The route guard now matches the sidebar: any user with `settings:read` can open the page, and the individual tabs / cards continue to enforce their own per-feature permissions (`users:read`, `groups:update`, `oidc:*`, etc. — many of them admin-only, some not). Group editor routes moved to permission-based guards too (`groups:create` for `/groups/new`, `groups:update` for `/groups/:id/edit`), so permission delegation works end-to-end. Admins retain full access since admins implicitly hold every permission.
|
|
- **Settings page: permission-gated instead of admin-only** — the Settings sidebar entry has always been visible to any user holding `settings:read`, but the route guard required admin role, so a non-admin with `settings:read` would see the entry, click it, and get silently redirected back to the dashboard. The route guard now matches the sidebar: any user with `settings:read` can open the page, and the individual tabs / cards continue to enforce their own per-feature permissions (`users:read`, `groups:update`, `oidc:*`, etc. — many of them admin-only, some not). Group editor routes moved to permission-based guards too (`groups:create` for `/groups/new`, `groups:update` for `/groups/:id/edit`), so permission delegation works end-to-end. Admins retain full access since admins implicitly hold every permission.
|
|
|
|
|
|
|
|
### Fixed
|
|
### Fixed
|
|
|
|
|
+- **H2C dual-nozzle detection missed post-2026 serial batches** ([#1105](https://github.com/maziggy/bambuddy/issues/1105)) — Bambu has started shipping H2C units with a new serial prefix (`31B8B…` observed on a January 2026 unit) instead of the legacy `094…` shared by the H2D/H2C/H2S family. The K-profile edit flow (`backend/app/api/routes/kprofiles.py`) and the delete-K-profile MQTT path (`backend/app/services/bambu_mqtt.py::delete_kprofile`) branch on serial prefix to pick the dual-nozzle command format, so units with the new prefix were silently falling into the single-nozzle branch and getting the wrong K-profile payload shape. Added `31B8B` (5-char match covering the model code + revision bytes, leaving the revision-letter slot free to iterate) alongside the existing `094` and `20P9` prefixes; runtime paths that auto-detect dual-nozzle from `device.extruder.info` were already prefix-agnostic. New regression test `test_h2c_new_prefix_uses_dual_nozzle_format` in `test_bambu_mqtt.py`. Thanks to @m4rtini2 for the report.
|
|
|
- **Spoolman iframe silently blank on HTTPS Bambuddy with HTTP Spoolman** ([#1096](https://github.com/maziggy/bambuddy/issues/1096)) — Users behind an HTTPS reverse proxy (Traefik / Nginx / Caddy) pointing the Spoolman URL at plain HTTP saw the Filament tab render as a blank page with only a console-side `Mixed Content` warning. CSP was fine (the `#1054` fix already allowed `frame-src http:`), but browsers enforce mixed-content blocking independently of CSP — an HTTP iframe inside an HTTPS parent is always blocked. Bambuddy can't technically fix this (the browser is correct to refuse), so instead of the silent blank frame the Filament page now detects the protocol mismatch (`window.location.protocol === 'https:'` plus Spoolman URL starting with `http://`) and renders an inline warning card explaining the root cause, pointing users at the right fix (put Spoolman behind the same HTTPS reverse proxy and update the Spoolman URL in Settings), and offering an "Open Spoolman in a new tab" button as an immediate workaround — a standalone tab isn't subject to mixed-content rules. Localised across all 8 UI languages. Thanks to @jsapede for the report.
|
|
- **Spoolman iframe silently blank on HTTPS Bambuddy with HTTP Spoolman** ([#1096](https://github.com/maziggy/bambuddy/issues/1096)) — Users behind an HTTPS reverse proxy (Traefik / Nginx / Caddy) pointing the Spoolman URL at plain HTTP saw the Filament tab render as a blank page with only a console-side `Mixed Content` warning. CSP was fine (the `#1054` fix already allowed `frame-src http:`), but browsers enforce mixed-content blocking independently of CSP — an HTTP iframe inside an HTTPS parent is always blocked. Bambuddy can't technically fix this (the browser is correct to refuse), so instead of the silent blank frame the Filament page now detects the protocol mismatch (`window.location.protocol === 'https:'` plus Spoolman URL starting with `http://`) and renders an inline warning card explaining the root cause, pointing users at the right fix (put Spoolman behind the same HTTPS reverse proxy and update the Spoolman URL in Settings), and offering an "Open Spoolman in a new tab" button as an immediate workaround — a standalone tab isn't subject to mixed-content rules. Localised across all 8 UI languages. Thanks to @jsapede for the report.
|
|
|
- **Reprint-from-Archive left `created_by_id` as `NULL`** ([#730](https://github.com/maziggy/bambuddy/issues/730) follow-up) — 0.2.4b1 fixed user attribution for Direct Print / File Manager / Library prints, but the reprint path was still unattributed on the *archive* row. Reprint intentionally reuses the source archive (to avoid duplicate rows — see `register_expected_print`), so an archive auto-created from a printer-initiated print with no known user stayed `created_by_id=NULL` forever, even after multiple reprints by authenticated Bambuddy users. Print Log got the reprinter's username correctly (via `_print_user_info`), but the Statistics per-user filter — which reads `archive.created_by_id` — kept showing the archive as unassigned. Fix in `main.py`'s print-complete handler: when the archive has no `created_by_id` and a print-session user is set (which reprint always sets via `set_current_print_user`), back-fill the archive's attribution. Never overwrites an existing attribution — the original uploader keeps ownership; NULL archives are the only ones touched. Thanks to @3823u44238 for the detailed retest that caught this.
|
|
- **Reprint-from-Archive left `created_by_id` as `NULL`** ([#730](https://github.com/maziggy/bambuddy/issues/730) follow-up) — 0.2.4b1 fixed user attribution for Direct Print / File Manager / Library prints, but the reprint path was still unattributed on the *archive* row. Reprint intentionally reuses the source archive (to avoid duplicate rows — see `register_expected_print`), so an archive auto-created from a printer-initiated print with no known user stayed `created_by_id=NULL` forever, even after multiple reprints by authenticated Bambuddy users. Print Log got the reprinter's username correctly (via `_print_user_info`), but the Statistics per-user filter — which reads `archive.created_by_id` — kept showing the archive as unassigned. Fix in `main.py`'s print-complete handler: when the archive has no `created_by_id` and a print-session user is set (which reprint always sets via `set_current_print_user`), back-fill the archive's attribution. Never overwrites an existing attribution — the original uploader keeps ownership; NULL archives are the only ones touched. Thanks to @3823u44238 for the detailed retest that caught this.
|
|
|
- **Settings: failed-save toast looped forever when the user lacked `settings:update`** — the Settings page runs a debounced auto-save effect that fires `PATCH /settings` whenever `localSettings` diverges from the last server snapshot. When a delegated user with `settings:read` but not `settings:update` toggled a control, the effect fired `PATCH`, got `403`, and kept re-firing every ~500 ms producing an endless stream of identical "Failed to save" toasts. Gated at three points so the mutation is never attempted without permission: (1) the `updateSetting` callback — every onChange path — shows one `settings.toast.noPermissionUpdate` toast and short-circuits before diverging `localSettings`; (2) the debounced-save effect safety-nets the same check in case any call site bypassed `updateSetting`; (3) the language `<select>` was a fire-and-forget direct `api.updateSettings` call that always flashed a success toast regardless of outcome — it now goes through `updateMutation` with the same permission guard. New `settings.toast.noPermissionUpdate` key added across all 8 locales with full translations (not English-fallback).
|
|
- **Settings: failed-save toast looped forever when the user lacked `settings:update`** — the Settings page runs a debounced auto-save effect that fires `PATCH /settings` whenever `localSettings` diverges from the last server snapshot. When a delegated user with `settings:read` but not `settings:update` toggled a control, the effect fired `PATCH`, got `403`, and kept re-firing every ~500 ms producing an endless stream of identical "Failed to save" toasts. Gated at three points so the mutation is never attempted without permission: (1) the `updateSetting` callback — every onChange path — shows one `settings.toast.noPermissionUpdate` toast and short-circuits before diverging `localSettings`; (2) the debounced-save effect safety-nets the same check in case any call site bypassed `updateSetting`; (3) the language `<select>` was a fire-and-forget direct `api.updateSettings` call that always flashed a success toast regardless of outcome — it now goes through `updateMutation` with the same permission guard. New `settings.toast.noPermissionUpdate` key added across all 8 locales with full translations (not English-fallback).
|