From 219af65c68300247b90b34bfaed1a22b023ebcdd Mon Sep 17 00:00:00 2001 From: maziggy Date: Thu, 23 Apr 2026 14:35:02 +0200 Subject: [PATCH] fix(#1096): warn on Spoolman HTTP/HTTPS mismatch instead of silent blank iframe Users behind an HTTPS reverse proxy pointing the Spoolman URL at plain HTTP saw the Filament tab render as a blank page with only a console- side Mixed Content warning. Browsers block HTTP iframes inside HTTPS parents by design (independent of CSP; #1054's frame-src http: fix only helps when the parent is also HTTP). The fix for the user's setup is to put Spoolman behind the same reverse proxy with HTTPS. Bambuddy can't override the browser's mixed-content block, but it can stop rendering an iframe that will silently fail. When window.location.protocol is https: and the Spoolman URL starts with http://, render a warning card explaining the root cause and offering an "Open in new tab" fallback (standalone tabs aren't subject to mixed-content rules). Localised across all 8 UI languages. --- CHANGELOG.md | 1 + frontend/src/i18n/locales/de.ts | 5 ++ frontend/src/i18n/locales/en.ts | 5 ++ frontend/src/i18n/locales/fr.ts | 5 ++ frontend/src/i18n/locales/it.ts | 5 ++ frontend/src/i18n/locales/ja.ts | 5 ++ frontend/src/i18n/locales/pt-BR.ts | 5 ++ frontend/src/i18n/locales/zh-CN.ts | 5 ++ frontend/src/i18n/locales/zh-TW.ts | 5 ++ frontend/src/pages/InventoryPage.tsx | 43 ++++++++++++++++- .../{index-DJb_CJSk.js => index-Cl5t9soF.js} | 46 +++++++++---------- static/index.html | 2 +- 12 files changed, 107 insertions(+), 25 deletions(-) rename static/assets/{index-DJb_CJSk.js => index-Cl5t9soF.js} (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cd59a541..81d0c87c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,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. ### Fixed +- **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. - **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 ` without a
');let c=t.getAttribute("formaction")||l.getAttribute("action");if(r=c?bm(c,e):null,n=t.getAttribute("formmethod")||l.getAttribute("method")||zk,i=jM(t.getAttribute("formenctype"))||jM(l.getAttribute("enctype"))||Uk,s=new FormData(l,t),!she()){let{name:d,type:u,value:m}=t;if(u==="image"){let p=d?`${d}.`:"";s.append(`${p}x`,"0"),s.append(`${p}y`,"0")}else d&&s.append(d,m)}}else{if(qP(t))throw new Error('Cannot submit element that is not ,