feat: Improve initial load experience with placeholder config

This commit is contained in:
courtmanr@gmail.com
2025-05-05 08:42:17 +01:00
parent 01ebe3519f
commit 0e7928c9c1
3 changed files with 51 additions and 33 deletions
+14 -10
View File
@@ -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
+25 -22
View File
@@ -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)
+12 -1
View File
@@ -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';
}