166 lines
7.6 KiB
JavaScript
166 lines
7.6 KiB
JavaScript
// --- 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 };
|