Explorar el Código

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.
maziggy hace 2 meses
padre
commit
1df5130df2

+ 1 - 0
CHANGELOG.md

@@ -18,6 +18,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **WiFi Safeguard for SpoolBuddy Pi** — The install script now drops an APT hook (`/etc/apt/apt.conf.d/80-preserve-wifi`) that backs up NetworkManager WiFi connections before every `apt upgrade` and restores them if they get wiped. Prevents headless SpoolBuddy Pis from losing WiFi connectivity after Raspberry Pi OS package upgrades (observed with Bookworm kernel/raspi-config updates that clear `/etc/NetworkManager/system-connections/`).
 - **WiFi Safeguard for SpoolBuddy Pi** — The install script now drops an APT hook (`/etc/apt/apt.conf.d/80-preserve-wifi`) that backs up NetworkManager WiFi connections before every `apt upgrade` and restores them if they get wiped. Prevents headless SpoolBuddy Pis from losing WiFi connectivity after Raspberry Pi OS package upgrades (observed with Bookworm kernel/raspi-config updates that clear `/etc/NetworkManager/system-connections/`).
 - **SpoolBuddy Install Script Now Upgrades System Packages** — The install script now runs `apt-get upgrade -y` after installing required packages and the WiFi safeguard. This ensures the Pi is fully up to date before SpoolBuddy is deployed, and the WiFi safeguard protects connectivity during the upgrade.
 - **SpoolBuddy Install Script Now Upgrades System Packages** — The install script now runs `apt-get upgrade -y` after installing required packages and the WiFi safeguard. This ensures the Pi is fully up to date before SpoolBuddy is deployed, and the WiFi safeguard protects connectivity during the upgrade.
 - **SpoolBuddy Assign-to-AMS Material Mismatch Warnings** — The SpoolBuddy "Assign to AMS" modal now warns when the spool's material or slicer profile doesn't match the target slot's current filament. Shows a confirmation dialog with five warning levels: exact material mismatch, partial material match, profile-only mismatch, and combined material+profile mismatches. Respects the global `disable_filament_warnings` setting. Previously, assigning a spool to an occupied slot proceeded without any validation, matching the behavior already present in the main Assign Spool modal.
 - **SpoolBuddy Assign-to-AMS Material Mismatch Warnings** — The SpoolBuddy "Assign to AMS" modal now warns when the spool's material or slicer profile doesn't match the target slot's current filament. Shows a confirmation dialog with five warning levels: exact material mismatch, partial material match, profile-only mismatch, and combined material+profile mismatches. Respects the global `disable_filament_warnings` setting. Previously, assigning a spool to an occupied slot proceeded without any validation, matching the behavior already present in the main Assign Spool modal.
+- **Spool Assignment Changes Sync Across Tabs** — Assigning or unassigning a spool now broadcasts a WebSocket event to all connected clients. Other open browser tabs and the SpoolBuddy frontend update automatically without requiring a page reload.
 
 
 ### Fixed
 ### Fixed
 - **Delete Tag Leaves Stale Tag Type** — The "Delete Tag" button in the spool edit modal only cleared `tag_uid` but left `tray_uuid`, `tag_type`, and `data_origin` intact. All tag-related fields are now cleared together.
 - **Delete Tag Leaves Stale Tag Type** — The "Delete Tag" button in the spool edit modal only cleared `tag_uid` but left `tray_uuid`, `tag_type`, and `data_origin` intact. All tag-related fields are now cleared together.

+ 21 - 0
backend/app/api/routes/inventory.py

@@ -13,6 +13,7 @@ from backend.app.core.auth import RequirePermissionIfAuthEnabled
 from backend.app.core.catalog_defaults import DEFAULT_COLOR_CATALOG, DEFAULT_SPOOL_CATALOG
 from backend.app.core.catalog_defaults import DEFAULT_COLOR_CATALOG, DEFAULT_SPOOL_CATALOG
 from backend.app.core.database import get_db
 from backend.app.core.database import get_db
 from backend.app.core.permissions import Permission
 from backend.app.core.permissions import Permission
+from backend.app.core.websocket import ws_manager
 from backend.app.models.ams_label import AmsLabel
 from backend.app.models.ams_label import AmsLabel
 from backend.app.models.color_catalog import ColorCatalogEntry
 from backend.app.models.color_catalog import ColorCatalogEntry
 from backend.app.models.spool import Spool
 from backend.app.models.spool import Spool
@@ -1100,6 +1101,16 @@ async def assign_spool(
     resp = result.scalar_one()
     resp = result.scalar_one()
     response = SpoolAssignmentResponse.model_validate(resp)
     response = SpoolAssignmentResponse.model_validate(resp)
     response.configured = configured
     response.configured = configured
+
+    await ws_manager.broadcast(
+        {
+            "type": "spool_assignment_changed",
+            "printer_id": data.printer_id,
+            "ams_id": data.ams_id,
+            "tray_id": data.tray_id,
+        }
+    )
+
     return response
     return response
 
 
 
 
@@ -1125,6 +1136,16 @@ async def unassign_spool(
 
 
     await db.delete(assignment)
     await db.delete(assignment)
     await db.commit()
     await db.commit()
+
+    await ws_manager.broadcast(
+        {
+            "type": "spool_assignment_changed",
+            "printer_id": printer_id,
+            "ams_id": ams_id,
+            "tray_id": tray_id,
+        }
+    )
+
     return {"status": "deleted"}
     return {"status": "deleted"}
 
 
 
 

+ 12 - 6
backend/tests/unit/services/test_printer_manager.py

@@ -1163,17 +1163,23 @@ class TestHasStgCurIdleBug:
         assert has_stg_cur_idle_bug("a1") is True  # case insensitive
         assert has_stg_cur_idle_bug("a1") is True  # case insensitive
         assert has_stg_cur_idle_bug("a1 mini") is True
         assert has_stg_cur_idle_bug("a1 mini") is True
 
 
-    def test_a1_internal_codes_return_true(self):
-        """Verify A1 internal model codes return True."""
+    def test_p1_models_return_true(self):
+        """Verify P1P/P1S model variants return True."""
+        assert has_stg_cur_idle_bug("P1P") is True
+        assert has_stg_cur_idle_bug("P1S") is True
+        assert has_stg_cur_idle_bug("p1p") is True  # case insensitive
+
+    def test_internal_codes_return_true(self):
+        """Verify internal model codes return True."""
         assert has_stg_cur_idle_bug("N1") is True  # A1 Mini
         assert has_stg_cur_idle_bug("N1") is True  # A1 Mini
         assert has_stg_cur_idle_bug("N2S") is True  # A1
         assert has_stg_cur_idle_bug("N2S") is True  # A1
+        assert has_stg_cur_idle_bug("C11") is True  # P1P
+        assert has_stg_cur_idle_bug("C12") is True  # P1S
 
 
-    def test_non_a1_models_return_false(self):
-        """Verify non-A1 models return False."""
+    def test_non_affected_models_return_false(self):
+        """Verify non-affected models return False."""
         assert has_stg_cur_idle_bug("X1C") is False
         assert has_stg_cur_idle_bug("X1C") is False
         assert has_stg_cur_idle_bug("X1") is False
         assert has_stg_cur_idle_bug("X1") is False
-        assert has_stg_cur_idle_bug("P1P") is False
-        assert has_stg_cur_idle_bug("P1S") is False
         assert has_stg_cur_idle_bug("H2D") is False
         assert has_stg_cur_idle_bug("H2D") is False
 
 
     def test_none_model_returns_false(self):
     def test_none_model_returns_false(self):

+ 6 - 0
frontend/src/hooks/useWebSocket.ts

@@ -263,6 +263,12 @@ export function useWebSocket() {
         }));
         }));
         break;
         break;
 
 
+      case 'spool_assignment_changed':
+        // Spool assigned/unassigned - refresh assignment data across all tabs
+        debouncedInvalidate('spool-assignments');
+        debouncedInvalidate('slotPresets');
+        break;
+
       case 'spool_auto_assigned':
       case 'spool_auto_assigned':
         // RFID tag matched - refresh inventory and assignment data
         // RFID tag matched - refresh inventory and assignment data
         debouncedInvalidate('inventory-spools');
         debouncedInvalidate('inventory-spools');

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
static/assets/index-BKqUb3-A.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-CS1ycNG7.js"></script>
+    <script type="module" crossorigin src="/assets/index-BKqUb3-A.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-CZLTApPU.css">
     <link rel="stylesheet" crossorigin href="/assets/index-CZLTApPU.css">
   </head>
   </head>
   <body>
   <body>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio