From 559d41cc5ea8bb19bab0c9e877ea898b408e5f7a Mon Sep 17 00:00:00 2001 From: maziggy Date: Mon, 23 Mar 2026 14:59:12 +0100 Subject: [PATCH] Fix SpoolBuddy update UI: loading states, page reload, stale data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check button had no visual feedback because isFetching doesn't trigger reliably with cached data — replaced with manual loading state. Removed kiosk restart via getty; the frontend now detects daemon re-registration via WebSocket and calls window.location.reload(), keeping the user on the same page and fetching all fresh data. --- CHANGELOG.md | 3 +- backend/app/services/spoolbuddy_ssh.py | 11 ++---- .../spoolbuddy/SpoolBuddySettingsPage.tsx | 36 +++++++++++++++---- .../{index-Cj4TkOE2.js => index-B8Ecu_qy.js} | 2 +- static/index.html | 2 +- 5 files changed, 34 insertions(+), 20 deletions(-) rename static/assets/{index-Cj4TkOE2.js => index-B8Ecu_qy.js} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52abaa654..a5a70b145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,8 @@ All notable changes to Bambuddy will be documented in this file. ### Fixed - **SpoolBuddy Update Check Always Shows "Up to Date"** — The SpoolBuddy daemon update check compared the device's firmware version against GitHub releases instead of the running Bambuddy backend version. This meant the check could incorrectly report "up to date" even when the daemon was behind. Fixed by comparing directly against `APP_VERSION` from the backend config. - **SpoolBuddy Updates Now Use SSH** — Replaced the fragile self-update mechanism (daemon pulls its own code via git, permission errors on `.git/`, hardcoded `main` branch) with SSH-based updates driven by the Bambuddy backend. Bambuddy now SSHes into the SpoolBuddy Pi and runs git fetch/checkout, pip install, systemctl restart, and kiosk browser restart remotely. Updates automatically use the same branch as Bambuddy. SSH key pairing is fully automatic — Bambuddy generates an ED25519 keypair and includes the public key in the device registration response; the daemon deploys it to `authorized_keys` on first connect. The install script creates the `spoolbuddy` user with a bash shell and sudoers entries for daemon and kiosk restart. A "Force Update" button allows re-deploying even when versions match. The SSH public key is also shown in SpoolBuddy Settings → Updates → SSH Setup for manual pairing if needed. -- **SpoolBuddy Kiosk Shows Stale Frontend After Update** — After an update, the kiosk browser displayed the old frontend because Chromium served cached assets even after restarting. Added `--disk-cache-size=0` to the Chromium launch flags so it always loads fresh content from the Bambuddy backend. - **SpoolBuddy Kiosk Starts Before Network Is Ready** — On fresh installs, the kiosk browser launched before the network was fully up, showing a connection error for 10-15 seconds until connectivity was restored. The getty@tty1 autologin override now waits for `network-online.target` so Chromium has connectivity when it starts. -- **SpoolBuddy Update Status Stale After Restart** — After a SpoolBuddy update completed, the UI permanently showed "Update complete, daemon restarting..." and a stale "update available" banner. Root cause: the SSH update set status to `"complete"` after the daemon had already restarted and re-registered, overwriting the cleared state. Fixed by letting daemon re-registration be the completion signal — it now clears any update status (`pending`/`updating`/`complete`/`error`), and the final `"complete"` write was removed. Also added WebSocket handlers for `spoolbuddy_update` and `spoolbuddy_online` events so the frontend refreshes immediately. +- **SpoolBuddy Update UI Stale After Restart** — After a SpoolBuddy update, the UI permanently showed the old version and "update available" because: (1) the SSH update set status to `"complete"` after the daemon had already re-registered, overwriting the cleared state; (2) the kiosk restart navigated away from the updates page; (3) query cache served stale data. Fixed by letting daemon re-registration clear all update status, removing the kiosk restart in favor of a frontend-driven `window.location.reload()` triggered via WebSocket when the daemon comes back online, and adding proper loading states to Check/Force Update buttons. - **Virtual Printer Proxy A1 Printing Fails** ([#757](https://github.com/maziggy/bambuddy/issues/757)) — BambuStudio could not send prints to A1 (and potentially P1S) virtual printers in proxy mode. The slicer connects to undocumented proprietary ports 2024-2026 on these models, which the proxy was not forwarding, causing BambuStudio to show an access code dialog instead of printing. Added transparent TCP pass-through proxying for ports 2024-2026. These ports are silently ignored on models that don't use them (X1C, H2C, P2S). Reported by @Utility9298. - **Spool Assignment on Empty AMS Slots** ([#784](https://github.com/maziggy/bambuddy/issues/784)) — Empty AMS slots (no physical spool detected) showed "Assign Spool" and "Configure" buttons in the hover popup. Assigning a spool to an empty slot created a stuck state because no "Unassign" button is available for empty slots. Removed both buttons from empty AMS and HT AMS slot popups. External spool holders are unaffected. Reported by @RosdasHH. diff --git a/backend/app/services/spoolbuddy_ssh.py b/backend/app/services/spoolbuddy_ssh.py index 5d46d5721..090545cbe 100644 --- a/backend/app/services/spoolbuddy_ssh.py +++ b/backend/app/services/spoolbuddy_ssh.py @@ -225,15 +225,8 @@ async def perform_ssh_update(device_id: str, ip_address: str, install_path: str await _update_progress("error", f"Service restart failed: {stderr[:200]}") return - # Step 6: Restart kiosk browser to load updated frontend - rc, _, stderr = await _run_ssh_command( - ip_address, - "sudo /usr/bin/systemctl restart getty@tty1.service", - private_key, - ) - if rc != 0: - # Non-fatal — kiosk may not be set up on all devices - logger.warning("SpoolBuddy %s: kiosk restart failed (non-fatal): %s", device_id, stderr[:200]) + # No explicit kiosk restart — the frontend detects daemon re-registration + # via WebSocket and reloads itself automatically. logger.info("SpoolBuddy %s: SSH update complete (branch=%s)", device_id, branch) except Exception as e: diff --git a/frontend/src/pages/spoolbuddy/SpoolBuddySettingsPage.tsx b/frontend/src/pages/spoolbuddy/SpoolBuddySettingsPage.tsx index b785c146c..0bfbd790c 100644 --- a/frontend/src/pages/spoolbuddy/SpoolBuddySettingsPage.tsx +++ b/frontend/src/pages/spoolbuddy/SpoolBuddySettingsPage.tsx @@ -506,10 +506,23 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) { const [error, setError] = useState(null); const [sshExpanded, setSSHExpanded] = useState(false); const [copied, setCopied] = useState(false); + const [manualChecking, setManualChecking] = useState(false); + const wasUpdatingRef = useRef(false); const isUpdating = device.update_status === 'pending' || device.update_status === 'updating'; - const { data: updateResult, isFetching: checking, refetch } = useQuery({ + // Track when an update was in progress so we can reload when device comes back + useEffect(() => { + if (isUpdating) { + wasUpdatingRef.current = true; + } else if (wasUpdatingRef.current && device.update_status == null) { + // Status cleared = daemon re-registered after update. Reload for fresh state. + wasUpdatingRef.current = false; + window.location.reload(); + } + }, [isUpdating, device.update_status]); + + const { data: updateResult, refetch } = useQuery({ queryKey: ['spoolbuddy-update-check', device.device_id], queryFn: () => spoolbuddyApi.checkDaemonUpdate(device.device_id), staleTime: 30 * 1000, @@ -522,6 +535,15 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) { staleTime: Infinity, }); + const checkForUpdates = async () => { + setManualChecking(true); + try { + await refetch(); + } finally { + setManualChecking(false); + } + }; + const applyUpdate = async () => { setApplying(true); setError(null); @@ -606,11 +628,11 @@ function UpdatesTab({ device }: { device: SpoolBuddyDevice }) { /* Up to date → Check + Force buttons side by side */