// --- Server-Sent Events (SSE) Management --- // Note: Relies on several functions and variables being globally accessible: // - Global state: currentProjectData, selectedProjectName, eventSource // - State helpers: isProcessing, setSelectedProject // - UI functions: uiUpdateStatusBar, uiShowProcessingState, uiClearProcessingState, uiUpdateProjectSelector, uiClearProjectDisplay // - Core update functions: handleProjectChange, updateUIScadaCore, updateUIDrawingCore, updateUIConflictsCore // - Utility: debounce /** * Processes the full data update received from the SSE stream. * Updates the internal state and triggers UI updates based on the data. * @param {object} fullData - The complete data object from the server. */ function sseProcessUpdate(fullData) { console.log("[SSE] Received Full Data:", fullData); // Store the latest full data globally currentProjectData = {}; // Reset first const incomingProjects = fullData.projects || []; incomingProjects.forEach(projName => { currentProjectData[projName] = { status: fullData.status ? fullData.status[projName] : 'Unknown', last_commit: fullData.last_commit ? fullData.last_commit[projName] : 'N/A', // Ensure progress exists and has expected structure progress: (fullData.progress && fullData.progress[projName]) ? fullData.progress[projName] : { overall: {}, panels: {} } }; }); // --- Update Project Selector Dropdown --- const selectionChanged = uiUpdateProjectSelector(incomingProjects); if (selectionChanged) { // If the selection was forced (e.g., previous deleted, list empty) // handleProjectChange will update the UI for the new/empty selection console.log("[SSE] Project selection changed by dropdown update. Calling handleProjectChange."); handleProjectChange(); // Needs global access return; // Stop further processing here } // --- Process data for the currently selected project --- const currentSelection = document.getElementById('projectSelector')?.value; // Check selector exists // Update global state if needed (should normally match after selector update) if (selectedProjectName !== currentSelection) { setSelectedProject(currentSelection); // Needs global access } if (!selectedProjectName || !currentProjectData[selectedProjectName]) { console.log("[SSE] No project selected or no data available for selected project."); // Update UI to reflect lack of selection/data const projectSelector = document.getElementById('projectSelector'); if (projectSelector && projectSelector.options.length > 0 && projectSelector.value) { uiUpdateStatusBar(selectedProjectName, 'Waiting for data...', 'N/A'); uiShowProcessingState('Waiting for data...'); } else { uiUpdateStatusBar('...', 'No projects available', 'N/A'); uiClearProjectDisplay(); } return; } const projectData = currentProjectData[selectedProjectName]; // Update status bar immediately uiUpdateStatusBar(selectedProjectName, projectData.status, projectData.last_commit); // Update main UI based on processing state if (isProcessing(projectData.status)) { // Needs global access console.log(`[SSE] Project ${selectedProjectName} is processing.`); // If *any* project is processing, the global flag should remain true // (or be set to true if it wasn't already) isAnalysisGloballyActive = true; console.log(`[State] isAnalysisGloballyActive set/kept true due to processing status: "${projectData.status}"`); uiShowProcessingState(projectData.status); } else { console.log(`[SSE] Project ${selectedProjectName} is ready/error.`); // --- Update Global Flag: Check if ANY project is still processing --- let anyProjectStillProcessing = false; const projectNames = Object.keys(currentProjectData); for (const projName of projectNames) { if (currentProjectData[projName] && isProcessing(currentProjectData[projName].status)) { anyProjectStillProcessing = true; console.log(`[State] Project ${projName} is still processing. Keeping isAnalysisGloballyActive true.`); break; // Found one, no need to check others } } if (!anyProjectStillProcessing) { isAnalysisGloballyActive = false; console.log("[State] No projects are processing. isAnalysisGloballyActive set to false."); } // --- End Global Flag Update --- uiClearProcessingState(); // Call core redraws immediately const latestData = currentProjectData[selectedProjectName]; // Get current data if(latestData && !isProcessing(latestData.status)) { // Double-check status console.log("[SSE] Triggering immediate redraw after non-processing update."); updateUIScadaCore(latestData); // Needs global access updateUIDrawingCore(latestData); // Needs global access updateUIConflictsCore(latestData); // Needs global access } else { // If state somehow changed *back* to processing (unlikely here, but safe check) console.warn("[SSE] State seems to indicate processing immediately after non-processing update."); uiShowProcessingState(latestData?.status || "Processing..."); } } } // Create a debounced version of the process update function const debouncedProcessUpdate = debounce(sseProcessUpdate, 250); /** * Initializes the Server-Sent Events connection and sets up message handlers. */ function sseInitialize() { console.log("[SSE] Initializing connection..."); // Use global eventSource variable from state.js if (eventSource) { console.log("[SSE] Closing existing connection."); eventSource.close(); } try { eventSource = new EventSource("/stream"); eventSource.onmessage = function(event) { try { const data = JSON.parse(event.data); // Call the debounced processor debouncedProcessUpdate(data); } catch (error) { console.error("[SSE] Error parsing data:", error, "Data:", event.data); // Update UI to show error uiUpdateStatusBar(selectedProjectName, 'Error processing server update.', 'N/A'); } }; eventSource.onerror = function(err) { console.error("[SSE] Connection error:", err); // Update UI to show error uiUpdateStatusBar(selectedProjectName, 'Connection lost. Retrying...', 'N/A'); // Simple retry mechanism: close and re-initialize after a delay if (eventSource) { eventSource.close(); eventSource = null; // Nullify to allow re-creation } setTimeout(sseInitialize, 5000); // Attempt to reconnect after 5 seconds }; eventSource.onopen = function() { console.log("[SSE] Connection opened."); // Optionally update UI to show connected status uiUpdateStatusBar(selectedProjectName, "Connected. Waiting for updates...", currentProjectData[selectedProjectName]?.last_commit); }; console.log("[SSE] Event handlers set up."); } catch (error) { console.error("[SSE] Failed to create EventSource:", error); uiUpdateStatusBar(selectedProjectName, 'Failed to connect to server.', 'N/A'); } } // --- Export (if using modules) --- // export { sseInitialize };