diff --git a/server/configLoader.js b/server/configLoader.js index 40d7fe8f1..19fe34dfc 100644 --- a/server/configLoader.js +++ b/server/configLoader.js @@ -82,9 +82,11 @@ function loadPbsConfig(index = null) { function loadConfiguration() { // Only load .env file if not in test environment if (process.env.NODE_ENV !== 'test') { - require('dotenv').config(); + require('dotenv').config(); } + let isConfigPlaceholder = false; // Add this flag + // --- Proxmox Primary Endpoint Validation --- const primaryRequiredEnvVars = [ 'PROXMOX_HOST', @@ -103,20 +105,21 @@ function loadConfiguration() { } }); - if (missingVars.length > 0 || placeholderVars.length > 0) { + // Throw error only if required vars are MISSING + if (missingVars.length > 0) { let errorMessages = ['--- Configuration Error (Primary Endpoint) ---']; - if (missingVars.length > 0) { - errorMessages.push(`Missing required environment variables: ${missingVars.join(', ')}.`); - } - if (placeholderVars.length > 0) { - errorMessages.push(`The following primary environment variables seem to contain placeholder values: ${placeholderVars.join(', ')}.`); - } + errorMessages.push(`Missing required environment variables: ${missingVars.join(', ')}.`); errorMessages.push('Please ensure valid Proxmox connection details are provided.'); errorMessages.push('Refer to server/.env.example for the required variable names and format.'); - // Throw error instead of exiting throw new ConfigurationError(errorMessages.join('\n')); } + // Set the flag if placeholders were detected (but don't throw error) + if (placeholderVars.length > 0) { + isConfigPlaceholder = true; + console.warn(`WARN: Primary Proxmox environment variables seem to contain placeholder values: ${placeholderVars.join(', ')}. Pulse may not function correctly until configured.`); + } + // --- Load All Proxmox Endpoint Configurations --- const endpoints = []; @@ -201,7 +204,8 @@ function loadConfiguration() { } console.log('INFO: Configuration loaded successfully.'); - return { endpoints, pbsConfigs }; + // Return the flag along with endpoints and pbsConfigs + return { endpoints, pbsConfigs, isConfigPlaceholder }; } module.exports = { loadConfiguration, ConfigurationError }; // Export the function and error class \ No newline at end of file diff --git a/server/index.js b/server/index.js index 1014fb4a1..5262c8fd2 100644 --- a/server/index.js +++ b/server/index.js @@ -5,9 +5,10 @@ const { loadConfiguration, ConfigurationError } = require('./configLoader'); let endpoints; let pbsConfigs; +let isConfigPlaceholder = false; // Add global flag try { - ({ endpoints, pbsConfigs } = loadConfiguration()); + ({ endpoints, pbsConfigs, isConfigPlaceholder } = loadConfiguration()); } catch (error) { if (error instanceof ConfigurationError) { console.error(error.message); @@ -139,21 +140,27 @@ const io = new Server(server, { io.on('connection', (socket) => { console.log('Client connected'); // Immediately send current data if available - // if (cachedDiscoveryData) { - // socket.emit('rawData', cachedDiscoveryData); - // } + const currentState = stateManager.getState(); + if (stateManager.hasData()) { + // Include flag in rawData + socket.emit('rawData', { ...currentState, isConfigPlaceholder }); + } else { + console.log('No data available yet on connect, sending initial state.'); + // Include flag in initialState + socket.emit('initialState', { loading: true, isConfigPlaceholder }); + } socket.on('requestData', async () => { console.log('Client requested data'); try { - // Get current state const currentState = stateManager.getState(); if (stateManager.hasData()) { - socket.emit('rawData', currentState); + // Include flag in rawData + socket.emit('rawData', { ...currentState, isConfigPlaceholder }); } else { console.log('No data available yet on request, sending loading state.'); - // Send an explicit loading state if no data is available yet - socket.emit('initialState', { loading: true }); + // Include flag in initialState + socket.emit('initialState', { loading: true, isConfigPlaceholder }); // Optionally trigger an immediate discovery cycle? // runDiscoveryCycle(); // Be careful with triggering cycles on demand } @@ -203,19 +210,15 @@ async function runDiscoveryCycle() { // Update state using the state manager stateManager.updateDiscoveryData(discoveryData); - // currentNodes = discoveryData.nodes || []; - // currentVms = discoveryData.vms || []; - // currentContainers = discoveryData.containers || []; - // pbsDataArray = discoveryData.pbs || []; + // No need to store in global vars anymore // ... (logging summary) ... - const currentState = stateManager.getState(); - console.log(`[Discovery Cycle] Updated state. Nodes: ${currentState.nodes.length}, VMs: ${currentState.vms.length}, CTs: ${currentState.containers.length}, PBS: ${currentState.pbs.length}`); + const updatedState = stateManager.getState(); // Get the fully updated state + console.log(`[Discovery Cycle] Updated state. Nodes: ${updatedState.nodes.length}, VMs: ${updatedState.vms.length}, CTs: ${updatedState.containers.length}, PBS: ${updatedState.pbs.length}`); - // Emit combined data using updated global state + // Emit combined data using updated state manager state, including the flag if (io.engine.clientsCount > 0) { - // ... (emit rawData with currentNodes, currentVms, etc.) ... - io.emit('rawData', stateManager.getState()); + io.emit('rawData', { ...updatedState, isConfigPlaceholder }); } } catch (error) { console.error(`[Discovery Cycle] Error during execution: ${error.message}`, error.stack); @@ -246,12 +249,12 @@ async function runMetricCycle() { // Use imported fetchMetricsData const fetchedMetrics = await fetchMetricsData(runningVms, runningContainers, apiClients); - // Update global currentMetrics state + // Update metrics state if (fetchedMetrics && fetchedMetrics.length >= 0) { // Allow empty array to clear metrics - stateManager.updateMetricsData(fetchedMetrics); - console.log(`[Metrics Cycle] Updated metrics state for ${stateManager.getState().metrics.length} guests.`); - } else { - console.warn('[Metrics Cycle] fetchMetricsData returned unexpected value. Preserving previous metrics state.'); + stateManager.updateMetricsData(fetchedMetrics); + // Emit only metrics updates if needed, or rely on full rawData updates? + // Consider emitting a smaller 'metricsUpdate' event if performance is key + // io.emit('metricsUpdate', stateManager.getState().metrics); } // Emit rawData with updated global state (including metrics) diff --git a/src/public/js/socketHandler.js b/src/public/js/socketHandler.js index 5a8c0facc..f473a9927 100644 --- a/src/public/js/socketHandler.js +++ b/src/public/js/socketHandler.js @@ -52,6 +52,9 @@ PulseApp.socketHandler = (() => { function handleInitialState(state) { console.log('[socketHandler] Received initial state:', state); + // Store the placeholder flag + PulseApp.state.set('isConfigPlaceholder', state.isConfigPlaceholder || false); + if (state && state.loading) { // Update status text to indicate loading const statusText = document.getElementById('dashboard-status-text'); @@ -100,6 +103,9 @@ PulseApp.socketHandler = (() => { } + // Store the placeholder flag + PulseApp.state.set('isConfigPlaceholder', data.isConfigPlaceholder || false); + // --- Trigger UI update after processing data --- if (typeof updateAllUITablesRef === 'function') { updateAllUITablesRef(); @@ -147,7 +153,12 @@ PulseApp.socketHandler = (() => { if (loadingOverlay) { const loadingText = loadingOverlay.querySelector('p'); if (loadingText) { - loadingText.textContent = 'Connected. Reloading data...'; + // Check the flag before setting the text + if (PulseApp.state.get('isConfigPlaceholder')) { + loadingText.textContent = 'Configuration Required'; + } else { + loadingText.textContent = 'Connected. Reloading data...'; + } } loadingOverlay.style.display = 'flex'; }