From 591e273bc77d3ebf8a465de327b059b2317a0a97 Mon Sep 17 00:00:00 2001 From: Yinyin Liu Date: Wed, 11 Mar 2026 12:22:19 +0100 Subject: [PATCH] Fixed edit mode in the conflict of RabbitMQ(status message updates all entries) and WebSocket --- csharp/App/Backend/Database/Update.cs | 13 ++++++++++ .../App/Backend/Websockets/RabbitMQManager.cs | 3 +-- .../Backend/Websockets/WebsockerManager.cs | 24 ++++++++++++++----- .../content/dashboards/History/History.tsx | 6 +++++ .../contexts/InstallationsContextProvider.tsx | 13 +++++++--- 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/csharp/App/Backend/Database/Update.cs b/csharp/App/Backend/Database/Update.cs index 5ee1d011a..ed5b2cf6a 100644 --- a/csharp/App/Backend/Database/Update.cs +++ b/csharp/App/Backend/Database/Update.cs @@ -56,6 +56,19 @@ public static partial class Db } } + /// + /// Updates ONLY the Status column for an installation. + /// This avoids a full-row overwrite that can race with TestingMode changes. + /// + public static Boolean UpdateInstallationStatus(Int64 installationId, int status) + { + var rows = Connection.Execute( + "UPDATE Installation SET Status = ? WHERE Id = ?", + status, installationId); + if (rows > 0) Backup(); + return rows > 0; + } + // Ticket system public static Boolean Update(Ticket ticket) => Update(obj: ticket); public static Boolean Update(TicketAiDiagnosis diagnosis) => Update(obj: diagnosis); diff --git a/csharp/App/Backend/Websockets/RabbitMQManager.cs b/csharp/App/Backend/Websockets/RabbitMQManager.cs index f3d810920..2c8c65521 100644 --- a/csharp/App/Backend/Websockets/RabbitMQManager.cs +++ b/csharp/App/Backend/Websockets/RabbitMQManager.cs @@ -179,8 +179,7 @@ public static class RabbitMqManager Console.WriteLine("RECEIVED A HEARTBIT FROM prototype, time is "+ WebsocketManager.InstallationConnections[installationId].Timestamp); } - installation.Status = receivedStatusMessage.Status; - installation.Apply(Db.Update); + Db.UpdateInstallationStatus(installationId, receivedStatusMessage.Status); //Console.WriteLine("----------------------------------------------"); //If the status has changed, update all the connected front-ends regarding this installation diff --git a/csharp/App/Backend/Websockets/WebsockerManager.cs b/csharp/App/Backend/Websockets/WebsockerManager.cs index 0a8533f94..8d054b8b0 100644 --- a/csharp/App/Backend/Websockets/WebsockerManager.cs +++ b/csharp/App/Backend/Websockets/WebsockerManager.cs @@ -38,9 +38,7 @@ public static class WebsocketManager Console.WriteLine("installationConnection.Value.Timestamp is " + installationConnection.Value.Timestamp); installationConnection.Value.Status = (int)StatusType.Offline; - Installation installation = Db.Installations.FirstOrDefault(f => f.Product == installationConnection.Value.Product && f.Id == installationConnection.Key); - installation.Status = (int)StatusType.Offline; - installation.Apply(Db.Update); + Db.UpdateInstallationStatus(installationConnection.Key, (int)StatusType.Offline); if (installationConnection.Value.Connections.Count > 0) { idsToInform.Add(installationConnection.Key); @@ -61,17 +59,31 @@ public static class WebsocketManager public static async Task InformWebsocketsForInstallation(Int64 installationId) { var installation = Db.GetInstallationById(installationId); + if (installation is null) return; + byte[] dataToSend; List connections; lock (InstallationConnections) { - var installationConnection = InstallationConnections[installationId]; - Console.WriteLine("Update all the connected websockets for installation " + installation.Name); + if (!InstallationConnections.ContainsKey(installationId)) + { + Console.WriteLine($"InformWebsocketsForInstallation: No entry for installation {installationId}, skipping"); + return; + } - // Prune dead/closed connections before sending + var installationConnection = InstallationConnections[installationId]; + + // Prune dead/closed connections BEFORE checking count installationConnection.Connections.RemoveAll(c => c.State != WebSocketState.Open); + if (installationConnection.Connections.Count == 0) + { + Console.WriteLine($"InformWebsocketsForInstallation: No open connections for installation {installationId}, skipping"); + return; + } + Console.WriteLine("Update all the connected websockets for installation " + installation.Name); + var jsonObject = new { id = installationId, diff --git a/typescript/frontend-marios2/src/content/dashboards/History/History.tsx b/typescript/frontend-marios2/src/content/dashboards/History/History.tsx index 36e2693c0..268ae85d6 100644 --- a/typescript/frontend-marios2/src/content/dashboards/History/History.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/History/History.tsx @@ -30,6 +30,8 @@ import timezone from 'dayjs/plugin/timezone'; import EditIcon from '@mui/icons-material/Edit'; import DeleteIcon from '@mui/icons-material/Delete'; import { UserContext } from '../../../contexts/userContext'; +import { InstallationsContext } from '../../../contexts/InstallationsContextProvider'; +import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; dayjs.extend(utc); dayjs.extend(timezone); @@ -58,6 +60,8 @@ function HistoryOfActions(props: HistoryProps) { }); const context = useContext(UserContext); const { currentUser, setUser } = context; + const { fetchAllInstallations } = useContext(InstallationsContext); + const { product } = useContext(ProductIdContext); const [isRowHovered, setHoveredRow] = useState(-1); const [selectedAction, setSelectedAction] = useState(-1); const [editMode, setEditMode] = useState(false); @@ -109,6 +113,7 @@ function HistoryOfActions(props: HistoryProps) { if (res) { getHistory(); + fetchAllInstallations(product, false); setOpenModalAddAction(false); setEditMode(false); } @@ -129,6 +134,7 @@ function HistoryOfActions(props: HistoryProps) { if (res) { getHistory(); + fetchAllInstallations(product, false); } }; diff --git a/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx b/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx index 02bed5596..a663f4ae3 100644 --- a/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx +++ b/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx @@ -122,7 +122,7 @@ const InstallationsContextProvider = ({ useEffect(() => { const timer = setInterval(() => { applyBatchUpdates(); - }, 60000); + }, 2000); return () => clearInterval(timer); // Cleanup timer on component unmount }, [applyBatchUpdates]); @@ -156,8 +156,15 @@ const InstallationsContextProvider = ({ new_socket.addEventListener('message', (event) => { const message = JSON.parse(event.data); // Parse the JSON data - if (message.id !== -1) { - //For each received message (except the first one which is a batch, call the updateInstallationStatus function in order to import the message to the pendingUpdates list + + // Initial batch from backend is an array, subsequent updates are single objects + if (Array.isArray(message)) { + message.forEach((msg) => { + if (msg.id !== -1) { + updateInstallationStatus(msg.id, msg.status, msg.testingMode); + } + }); + } else if (message.id !== -1) { updateInstallationStatus( message.id, message.status,