425 lines
19 KiB
JavaScript
425 lines
19 KiB
JavaScript
// --- Event Listener Setup and Handlers ---
|
|
|
|
// Note: Relies on MANY globally accessible functions and variables:
|
|
// - State: setSelectedProject, selectedProject, selectedProjectName, currentProjectData
|
|
// - State helpers: isProcessing
|
|
// - UI: uiUpdateStatusBar, uiShowProcessingState, uiClearProcessingState, uiClearProjectDisplay,
|
|
// uiUpdateProjectNameDisplay, uiShowSection, uiShowStatusMessage (via specific wrappers like uiShowUploadStatus),
|
|
// uiClearManageFilesStatusMessages
|
|
// - API: apiAddProject, apiUploadPdfs, apiTriggerAnalysis, apiDeletePdf, apiDeleteProject, apiUploadManifest
|
|
// - Charting: chartManagerDestroyAll
|
|
// - Modals: modalManagerHide, modalManagerUpdateManageFilesTitle
|
|
// - Other Core Logic: handleLoadAndDisplayPdfs, updateUIScadaCore, updateUIDrawingCore, updateUIConflictsCore
|
|
|
|
// --- Core Handler Functions ---
|
|
|
|
/**
|
|
* Handles the change event for the project selector dropdown.
|
|
* Updates the global state and triggers a full UI refresh for the selected project.
|
|
*/
|
|
function handleProjectChange() {
|
|
// selectedProjectName should have been updated by the calling event listener
|
|
console.log(`[Events] Project selection changed to: ${selectedProjectName}`);
|
|
|
|
chartManagerDestroyAll(); // Clear charts immediately
|
|
|
|
const projectData = currentProjectData[selectedProjectName]; // Access global state
|
|
if (projectData) {
|
|
console.log(`[Events] Data found for ${selectedProjectName}. Status: ${projectData.status}`);
|
|
uiUpdateStatusBar(selectedProjectName, projectData.status, projectData.last_commit);
|
|
|
|
if (isProcessing(projectData.status)) {
|
|
console.log(`[Events] Project processing. Showing loading state.`);
|
|
uiShowProcessingState(projectData.status);
|
|
} else {
|
|
console.log(`[Events] Triggering redraw for newly selected project: ${selectedProjectName}`);
|
|
uiClearProcessingState(); // Clear any potential global loaders
|
|
// Draw immediately instead of using setTimeout
|
|
const latestData = currentProjectData[selectedProjectName];
|
|
if (latestData && !isProcessing(latestData.status)) {
|
|
updateUIScadaCore(latestData);
|
|
updateUIDrawingCore(latestData);
|
|
updateUIConflictsCore(latestData);
|
|
} else {
|
|
// If status is *still* processing (e.g., changed between checks), show loading
|
|
console.warn("[Events:ProjectChange] State indicates processing on immediate draw attempt.");
|
|
uiShowProcessingState(latestData?.status || "Processing...");
|
|
}
|
|
}
|
|
} else {
|
|
if (selectedProjectName) {
|
|
console.log(`[Events] No data found yet for selected project: ${selectedProjectName}. Showing loading.`);
|
|
uiUpdateStatusBar(selectedProjectName, 'Loading data...', 'N/A');
|
|
uiShowProcessingState('Loading data...');
|
|
} else {
|
|
console.log(`[Events] No project selected.`);
|
|
uiUpdateStatusBar('...', 'No project selected', 'N/A');
|
|
uiUpdateProjectNameDisplay('No Projects');
|
|
uiClearProjectDisplay();
|
|
}
|
|
}
|
|
// Manage Files button state is handled by the selector's listener directly
|
|
}
|
|
|
|
// --- Add Project Form Handler ---
|
|
|
|
async function handleAddProjectSubmit(event) {
|
|
event.preventDefault();
|
|
const form = event.target;
|
|
const statusDiv = document.getElementById('addProjectStatus');
|
|
const submitButton = form.querySelector('button[type="submit"]');
|
|
const modalElement = form.closest('.modal');
|
|
|
|
// UI update: Show submitting status
|
|
statusDiv.style.display = 'none'; // Use direct style manipulation or a UI function
|
|
statusDiv.textContent = '';
|
|
statusDiv.className = 'mt-3 alert alert-info';
|
|
statusDiv.textContent = 'Submitting project...';
|
|
statusDiv.style.display = 'block';
|
|
submitButton.disabled = true;
|
|
|
|
// Validation
|
|
const projectNameInput = document.getElementById('projectName');
|
|
const projectName = projectNameInput.value.trim();
|
|
const manifestFile = document.getElementById('manifestFile').files[0];
|
|
const pdfFiles = document.getElementById('pdfFiles').files;
|
|
let validationError = null;
|
|
if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) validationError = 'Invalid Project Name. Use only letters, numbers, underscores, or hyphens.';
|
|
else if (!manifestFile) validationError = 'Manifest CSV file is required.';
|
|
else if (pdfFiles.length === 0) validationError = 'At least one Drawing PDF file is required.';
|
|
|
|
if (validationError) {
|
|
statusDiv.className = 'mt-3 alert alert-danger';
|
|
statusDiv.textContent = validationError;
|
|
submitButton.disabled = false;
|
|
return;
|
|
}
|
|
|
|
// Call API
|
|
try {
|
|
const formData = new FormData(form);
|
|
const result = await apiAddProject(formData); // Use API module
|
|
statusDiv.className = 'mt-3 alert alert-success';
|
|
statusDiv.textContent = result.message || 'Project added successfully.';
|
|
form.reset();
|
|
modalManagerHide(modalElement); // Use Modal module
|
|
setTimeout(() => { submitButton.disabled = false; }, 500);
|
|
} catch (error) {
|
|
statusDiv.className = 'mt-3 alert alert-danger';
|
|
statusDiv.textContent = `Error: ${error.message || 'Could not add project.'}`;
|
|
submitButton.disabled = false;
|
|
}
|
|
}
|
|
|
|
|
|
// --- Manage Files Modal Action Handlers ---
|
|
|
|
async function handlePdfDeleteAction(projectName, filename) {
|
|
if (!confirm(`Are you sure you want to delete the file: ${filename}? This cannot be undone.`)) {
|
|
return;
|
|
}
|
|
console.log(`[Events] Requesting deletion of ${filename} from project ${projectName}`);
|
|
uiClearManageFilesStatusMessages();
|
|
uiShowManageFilesStatus(`Deleting ${filename}...`, 'info');
|
|
|
|
try {
|
|
const data = await apiDeletePdf(projectName, filename); // Use API module
|
|
uiShowManageFilesStatus(data.message || `Successfully deleted ${filename}.`, 'success');
|
|
await handleLoadAndDisplayPdfs(projectName); // Refresh list (function in script.js)
|
|
uiShowAnalysisTriggerStatus('File deleted. Trigger analysis to update progress.', 'info');
|
|
} catch (error) {
|
|
console.error('[Events] Error deleting PDF:', error);
|
|
uiShowManageFilesStatus(`Error deleting file: ${error.message}`, 'danger');
|
|
}
|
|
}
|
|
|
|
async function handlePdfUploadAction(form) {
|
|
// selectedProject accessed globally
|
|
if (!selectedProject) return;
|
|
|
|
const formData = new FormData(form);
|
|
const fileInput = document.getElementById('newPdfFiles');
|
|
|
|
if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
|
|
uiShowUploadStatus('Please select at least one PDF file to upload.', 'warning');
|
|
return;
|
|
}
|
|
|
|
console.log(`[Events] Uploading ${fileInput.files.length} file(s) to project ${selectedProject}`);
|
|
uiClearManageFilesStatusMessages();
|
|
uiShowUploadStatus('Uploading files...', 'info', false);
|
|
|
|
try {
|
|
const data = await apiUploadPdfs(selectedProject, formData); // Use API module
|
|
uiShowUploadStatus(data.message || `Successfully uploaded files.`, 'success');
|
|
form.reset();
|
|
await handleLoadAndDisplayPdfs(selectedProject); // Refresh list (function in script.js)
|
|
uiShowAnalysisTriggerStatus('Files uploaded. Trigger analysis to update progress.', 'info');
|
|
} catch (error) {
|
|
console.error('[Events] Error uploading PDFs:', error);
|
|
uiShowUploadStatus(`Upload failed: ${error.message}`, 'danger');
|
|
}
|
|
// Can't easily clear the 'uploading' message here if error occurs before final state
|
|
// Consider removing the direct DOM check from the original function
|
|
}
|
|
|
|
async function handleTriggerAnalysisAction() {
|
|
// selectedProject accessed globally
|
|
if (!selectedProject) return;
|
|
console.log(`[Events] Requesting manual analysis trigger for project ${selectedProject}`);
|
|
uiClearManageFilesStatusMessages();
|
|
// --- Set global flag to true ---
|
|
isAnalysisGloballyActive = true;
|
|
console.log(`[State] isAnalysisGloballyActive set to: ${isAnalysisGloballyActive}`);
|
|
// Show status in modal immediately
|
|
uiShowAnalysisTriggerStatus('Triggering analysis...', 'info', false);
|
|
|
|
// Also update main UI immediately to show processing state
|
|
const statusMsg = "Analysis triggered...";
|
|
const currentCommit = currentProjectData[selectedProject]?.last_commit || 'N/A'; // Get current commit if available
|
|
uiUpdateStatusBar(selectedProject, statusMsg, currentCommit);
|
|
uiShowProcessingState(statusMsg);
|
|
|
|
try {
|
|
const data = await apiTriggerAnalysis(selectedProject); // Use API module
|
|
// Update modal status on success, main UI is already showing processing
|
|
uiShowAnalysisTriggerStatus(data.message || 'Analysis triggered successfully. Monitor status bar for updates.', 'success', false);
|
|
} catch (error) {
|
|
console.error('[Events] Error triggering analysis:', error);
|
|
uiShowAnalysisTriggerStatus(`Error: ${error.message}`, 'danger');
|
|
// If trigger fails, reset global flag and clear processing state? Maybe.
|
|
isAnalysisGloballyActive = false; // Reset flag on error
|
|
uiClearProcessingState();
|
|
// Restore status bar to previous state? Or show error?
|
|
uiUpdateStatusBar(selectedProject, `Error triggering: ${error.message}`, currentCommit);
|
|
}
|
|
}
|
|
|
|
async function handleDeleteProjectAction() {
|
|
// selectedProject accessed globally
|
|
if (!selectedProject) {
|
|
uiShowDeleteProjectStatus('No project selected to delete.', 'warning');
|
|
return;
|
|
}
|
|
|
|
const confirmation = prompt(
|
|
`This action is IRREVERSIBLE and will permanently delete the project '${selectedProject}' and all its data (manifest, PDFs, text files, cloned repository) from the server.\n\nType the project name '${selectedProject}' below to confirm deletion:`
|
|
);
|
|
|
|
if (confirmation !== selectedProject) {
|
|
uiShowDeleteProjectStatus('Project deletion cancelled or confirmation mismatch.', 'info');
|
|
return;
|
|
}
|
|
|
|
console.log(`[Events] Requesting deletion of project ${selectedProject}`);
|
|
uiClearManageFilesStatusMessages();
|
|
uiShowDeleteProjectStatus(`Deleting project '${selectedProject}'...`, 'info', false);
|
|
const deleteBtn = document.getElementById('deleteProjectBtn'); // Direct access ok for disabling?
|
|
if (deleteBtn) deleteBtn.disabled = true;
|
|
|
|
try {
|
|
const data = await apiDeleteProject(selectedProject); // Use API module
|
|
uiShowDeleteProjectStatus(`Project '${selectedProject}' deleted successfully.`, 'success', false);
|
|
|
|
// Remove setTimeout for immediate UI update
|
|
// setTimeout(() => {
|
|
const manageModalElem = document.getElementById('manageFilesModal');
|
|
modalManagerHide(manageModalElem); // Use Modal module
|
|
|
|
// Update project selector UI
|
|
const selector = document.getElementById('projectSelector');
|
|
let newSelectedIndex = -1;
|
|
for (let i = 0; i < selector.options.length; i++) {
|
|
if (selector.options[i].value === selectedProject) {
|
|
newSelectedIndex = (i > 0) ? i - 1 : 0;
|
|
selector.remove(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update state and trigger UI refresh
|
|
if (selector.options.length > 0) {
|
|
selector.selectedIndex = newSelectedIndex >= 0 ? newSelectedIndex : 0;
|
|
setSelectedProject(selector.value);
|
|
handleProjectChange(); // Trigger full refresh
|
|
} else {
|
|
selector.innerHTML = '<option value="" disabled selected>No projects found</option>';
|
|
setSelectedProject(null);
|
|
uiUpdateProjectNameDisplay('No Projects');
|
|
uiUpdateStatusBar('...', 'No projects available', 'N/A');
|
|
uiClearProjectDisplay();
|
|
const manageFilesBtn = document.getElementById('manageFilesBtn');
|
|
if(manageFilesBtn) manageFilesBtn.disabled = true;
|
|
}
|
|
if (deleteBtn) deleteBtn.disabled = false; // Re-enable after operation
|
|
|
|
// }, 1500);
|
|
|
|
} catch (error) {
|
|
console.error('[Events] Error deleting project:', error);
|
|
uiShowDeleteProjectStatus(`Error: ${error.message}`, 'danger', false);
|
|
if (deleteBtn) deleteBtn.disabled = false; // Re-enable on error
|
|
}
|
|
}
|
|
|
|
async function handleManifestUploadAction(form) {
|
|
// selectedProject accessed globally
|
|
if (!selectedProject) return;
|
|
|
|
const formData = new FormData(form);
|
|
const fileInput = document.getElementById('newManifestFile');
|
|
|
|
// Validation
|
|
if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
|
|
uiShowUploadManifestStatus('Please select a manifest CSV file to upload.', 'warning');
|
|
return;
|
|
}
|
|
const manifestFile = fileInput.files[0];
|
|
if (!manifestFile.name.toLowerCase().endsWith('.csv')) {
|
|
uiShowUploadManifestStatus('Invalid file type. Please select a .csv file.', 'warning');
|
|
return;
|
|
}
|
|
|
|
console.log(`[Events] Uploading new manifest for project ${selectedProject}`);
|
|
uiClearManageFilesStatusMessages();
|
|
uiShowUploadManifestStatus('Uploading manifest...', 'info', false);
|
|
|
|
try {
|
|
const data = await apiUploadManifest(selectedProject, formData); // Use API module
|
|
uiShowUploadManifestStatus(data.message || 'Manifest updated successfully.', 'success');
|
|
form.reset();
|
|
uiShowAnalysisTriggerStatus('Manifest updated. Trigger analysis to see changes.', 'info');
|
|
} catch (error) {
|
|
console.error('[Events] Error uploading manifest:', error);
|
|
uiShowUploadManifestStatus(`Upload failed: ${error.message}`, 'danger');
|
|
}
|
|
}
|
|
|
|
|
|
// --- Initialization Function ---
|
|
|
|
/**
|
|
* Initializes the application state and attaches all necessary event listeners.
|
|
*/
|
|
function initializeEventListeners() {
|
|
console.log("[Events] Initializing event listeners and initial UI state...");
|
|
|
|
// --- Get DOM element references ---
|
|
const projectSelector = document.getElementById('projectSelector');
|
|
const manageFilesBtn = document.getElementById('manageFilesBtn');
|
|
const addProjectForm = document.getElementById('addProjectForm');
|
|
const manageFilesModalElement = document.getElementById('manageFilesModal');
|
|
const uploadPdfsForm = document.getElementById('uploadPdfsForm');
|
|
const triggerAnalysisBtn = document.getElementById('triggerAnalysisBtn');
|
|
const deleteProjectBtn = document.getElementById('deleteProjectBtn');
|
|
const uploadManifestForm = document.getElementById('uploadManifestForm');
|
|
const existingPdfListDiv = document.getElementById('existingPdfList');
|
|
|
|
// --- Setup Initial State & UI ---
|
|
// initialServerData is expected to be globally available from the HTML template
|
|
initialStatuses = initialServerData.status || {}; // Store initial statuses globally
|
|
|
|
if (projectSelector && projectSelector.options.length > 0 && projectSelector.value) {
|
|
setSelectedProject(projectSelector.value);
|
|
console.log("[Events] Initial project selected:", selectedProject);
|
|
if (manageFilesBtn) manageFilesBtn.disabled = !selectedProject;
|
|
// Set initial status bar text using potentially embedded initial status
|
|
const initialStatus = initialStatuses[selectedProject] || 'Initializing...';
|
|
uiUpdateStatusBar(selectedProject, initialStatus, 'N/A');
|
|
// Perform initial UI setup (will show loading if status is processing)
|
|
handleProjectChange();
|
|
} else {
|
|
console.log("[Events] No projects found initially or selector empty.");
|
|
setSelectedProject(null);
|
|
if (manageFilesBtn) manageFilesBtn.disabled = true;
|
|
uiUpdateProjectNameDisplay('No Projects');
|
|
uiUpdateStatusBar('...', 'No projects discovered.', 'N/A');
|
|
uiClearProjectDisplay();
|
|
}
|
|
|
|
// Show initial section (SCADA by default) after potentially setting initial state
|
|
uiShowSection('scada');
|
|
|
|
// --- Attach Event Listeners ---
|
|
|
|
// Project Selector
|
|
if (projectSelector) {
|
|
projectSelector.addEventListener('change', (event) => {
|
|
setSelectedProject(event.target.value);
|
|
handleProjectChange(); // Trigger full update
|
|
if (manageFilesBtn) manageFilesBtn.disabled = !selectedProject;
|
|
});
|
|
}
|
|
|
|
// Navigation Tabs
|
|
document.querySelectorAll('.nav-link[data-view]').forEach(link => {
|
|
link.addEventListener('click', (event) => {
|
|
event.preventDefault();
|
|
const targetSection = link.getAttribute('data-view');
|
|
uiShowSection(targetSection);
|
|
});
|
|
});
|
|
|
|
// Add Project Form
|
|
if (addProjectForm) {
|
|
addProjectForm.addEventListener('submit', handleAddProjectSubmit);
|
|
}
|
|
|
|
// Manage Files Modal: Show
|
|
if (manageFilesModalElement && manageFilesBtn) {
|
|
manageFilesModalElement.addEventListener('show.bs.modal', async () => {
|
|
// selectedProject accessed globally
|
|
if (!selectedProject) return;
|
|
modalManagerUpdateManageFilesTitle(selectedProject);
|
|
uiClearManageFilesStatusMessages();
|
|
await handleLoadAndDisplayPdfs(selectedProject); // Calls function in script.js
|
|
});
|
|
}
|
|
|
|
// Manage Files Modal: PDF Upload Form
|
|
if (uploadPdfsForm) {
|
|
uploadPdfsForm.addEventListener('submit', (event) => {
|
|
event.preventDefault();
|
|
handlePdfUploadAction(event.target);
|
|
});
|
|
}
|
|
|
|
// Manage Files Modal: Trigger Analysis Button
|
|
if (triggerAnalysisBtn) {
|
|
triggerAnalysisBtn.addEventListener('click', handleTriggerAnalysisAction);
|
|
}
|
|
|
|
// Manage Files Modal: Delete PDF Button (Event Delegation)
|
|
if (existingPdfListDiv) {
|
|
existingPdfListDiv.addEventListener('click', (event) => {
|
|
if (event.target.classList.contains('pdf-delete-btn')) {
|
|
const button = event.target;
|
|
const filename = button.dataset.filename;
|
|
const projectName = button.dataset.project;
|
|
if (filename && projectName) {
|
|
handlePdfDeleteAction(projectName, filename);
|
|
} else {
|
|
console.error("[Events] Missing filename or project name on delete button.", button);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Manage Files Modal: Delete Project Button
|
|
if (deleteProjectBtn) {
|
|
deleteProjectBtn.addEventListener('click', handleDeleteProjectAction);
|
|
}
|
|
|
|
// Manage Files Modal: Manifest Upload Form
|
|
if (uploadManifestForm) {
|
|
uploadManifestForm.addEventListener('submit', (event) => {
|
|
event.preventDefault();
|
|
handleManifestUploadAction(event.target);
|
|
});
|
|
}
|
|
|
|
console.log("[Events] Event listeners attached.");
|
|
}
|
|
|
|
// --- Export (if using modules) ---
|
|
// export { initializeEventListeners };
|