Files
pulse/src/public/js/socketHandler.js
T

393 lines
12 KiB
JavaScript

PulseApp.socketHandler = (() => {
let socket = null;
let isConnected = false;
let reconnectAttempts = 0;
const maxReconnectAttempts = 10;
const reconnectDelay = 2000; // 2 seconds
function init() {
console.log('[Socket] Initializing socket connection...');
createSocket();
}
function createSocket() {
if (socket) {
socket.removeAllListeners();
socket.disconnect();
}
socket = io();
window.socket = socket; // Make socket available globally for alerts
setupEventListeners();
}
function setupEventListeners() {
socket.on('connect', handleConnect);
socket.on('disconnect', handleDisconnect);
socket.on('rawData', handleRawData);
socket.on('initialState', handleInitialState);
socket.on('requestError', handleRequestError);
// Enhanced monitoring events
socket.on('alert', handleAlert);
socket.on('alertResolved', handleAlertResolved);
// Development features
socket.on('hotReload', handleHotReload);
// Handle connection errors
socket.on('connect_error', handleConnectError);
socket.on('reconnect', handleReconnect);
socket.on('reconnect_error', handleReconnectError);
socket.on('reconnect_failed', handleReconnectFailed);
}
function handleConnect() {
console.log('[Socket] Connected to server');
isConnected = true;
reconnectAttempts = 0;
updateConnectionStatus('connected');
// Request initial data
socket.emit('requestData');
}
function handleDisconnect(reason) {
console.log('[Socket] Disconnected from server:', reason);
isConnected = false;
updateConnectionStatus('disconnected');
// If it's not a planned disconnect, try to reconnect
if (reason !== 'io client disconnect') {
attemptReconnect();
}
}
function handleRawData(data) {
try {
// Update main application state
if (PulseApp.state) {
PulseApp.state.updateState(data);
}
// Update alerts system with new state
if (PulseApp.alerts && data.alerts) {
PulseApp.alerts.updateAlertsFromState(data);
}
// Process UI updates based on tab
updateUIFromData(data);
} catch (error) {
console.error('[Socket] Error processing raw data:', error);
}
}
function handleInitialState(data) {
console.log('[Socket] Received initial state:', data);
try {
if (PulseApp.state) {
PulseApp.state.updateState(data);
}
updateUIFromData(data);
} catch (error) {
console.error('[Socket] Error processing initial state:', error);
}
}
function handleRequestError(error) {
console.error('[Socket] Request error:', error);
updateConnectionStatus('error');
}
function handleAlert(alert) {
console.log('[Socket] Received alert:', alert);
// Forward to alerts handler
if (PulseApp.alerts) {
// The alerts handler will be called directly from its socket listeners
// This is just for any additional processing
}
}
function handleAlertResolved(alert) {
console.log('[Socket] Alert resolved:', alert);
// Forward to alerts handler
if (PulseApp.alerts) {
// The alerts handler will be called directly from its socket listeners
// This is just for any additional processing
}
}
function handleHotReload() {
console.log('[Socket] Hot reload triggered');
if (process.env.NODE_ENV === 'development') {
window.location.reload();
}
}
function handleConnectError(error) {
console.error('[Socket] Connection error:', error);
updateConnectionStatus('error');
}
function handleReconnect() {
console.log('[Socket] Reconnected successfully');
reconnectAttempts = 0;
updateConnectionStatus('connected');
}
function handleReconnectError(error) {
console.error('[Socket] Reconnection error:', error);
reconnectAttempts++;
updateConnectionStatus('reconnecting');
}
function handleReconnectFailed() {
console.error('[Socket] Reconnection failed - max attempts reached');
updateConnectionStatus('failed');
}
function attemptReconnect() {
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
updateConnectionStatus('reconnecting');
setTimeout(() => {
console.log(`[Socket] Attempting to reconnect... (${reconnectAttempts}/${maxReconnectAttempts})`);
socket.connect();
}, reconnectDelay * reconnectAttempts); // Exponential backoff
} else {
updateConnectionStatus('failed');
}
}
function updateConnectionStatus(status) {
const statusElement = document.getElementById('connection-status');
if (!statusElement) return;
// Clear previous classes
statusElement.className = statusElement.className
.replace(/\b(connected|disconnected|reconnecting|error|failed)\b/g, '')
.trim();
let statusText, statusClass;
switch (status) {
case 'connected':
statusText = 'Connected';
statusClass = 'connected text-xs px-2 py-1 rounded-full bg-green-100 dark:bg-green-900 text-green-600 dark:text-green-400';
break;
case 'disconnected':
statusText = 'Disconnected';
statusClass = 'disconnected text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400';
break;
case 'reconnecting':
statusText = `Reconnecting... (${reconnectAttempts}/${maxReconnectAttempts})`;
statusClass = 'reconnecting text-xs px-2 py-1 rounded-full bg-yellow-100 dark:bg-yellow-900 text-yellow-600 dark:text-yellow-400 animate-pulse';
break;
case 'error':
statusText = 'Connection Error';
statusClass = 'error text-xs px-2 py-1 rounded-full bg-red-100 dark:bg-red-900 text-red-600 dark:text-red-400';
break;
case 'failed':
statusText = 'Connection Failed';
statusClass = 'failed text-xs px-2 py-1 rounded-full bg-red-100 dark:bg-red-900 text-red-600 dark:text-red-400';
break;
default:
statusText = 'Unknown';
statusClass = 'text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400';
}
statusElement.textContent = statusText;
statusElement.className = statusClass;
}
function updateUIFromData(data) {
try {
// Hide loading overlay when we receive data
const loadingOverlay = document.getElementById('loading-overlay');
if (loadingOverlay && (data.nodes || data.vms || data.containers || data.pbs)) {
loadingOverlay.style.display = 'none';
}
// Update different UI sections based on current tab
const activeTab = document.querySelector('.tab.active');
if (!activeTab) return;
const tabName = activeTab.getAttribute('data-tab');
switch (tabName) {
case 'main':
updateMainTab(data);
break;
case 'storage':
updateStorageTab(data);
break;
case 'pbs':
updatePbsTab(data);
break;
case 'backups':
updateBackupsTab(data);
break;
}
// Update performance indicators if available
updatePerformanceIndicators(data);
} catch (error) {
console.error('[Socket] Error updating UI from data:', error);
}
}
function updateMainTab(data) {
try {
// Update node summary cards
if (PulseApp.ui && PulseApp.ui.nodes && data.nodes) {
PulseApp.ui.nodes.updateNodeSummaryCards(data.nodes);
}
// Update main dashboard
if (PulseApp.ui && PulseApp.ui.dashboard) {
PulseApp.ui.dashboard.updateDashboardTable();
}
} catch (error) {
console.error('[Socket] Error updating main tab:', error);
}
}
function updateStorageTab(data) {
try {
if (PulseApp.ui && PulseApp.ui.storage && data.nodes) {
PulseApp.ui.storage.updateStorageInfo();
}
} catch (error) {
console.error('[Socket] Error updating storage tab:', error);
}
}
function updatePbsTab(data) {
try {
if (PulseApp.ui && PulseApp.ui.pbs && data.pbs) {
PulseApp.ui.pbs.updatePbsInfo(data.pbs);
}
} catch (error) {
console.error('[Socket] Error updating PBS tab:', error);
}
}
function updateBackupsTab(data) {
try {
if (PulseApp.ui && PulseApp.ui.backups && data.pbs) {
PulseApp.ui.backups.updateBackupsTab();
}
} catch (error) {
console.error('[Socket] Error updating backups tab:', error);
}
}
function updatePerformanceIndicators(data) {
try {
// Update performance stats if available
if (data.stats) {
updateStatsDisplay(data.stats);
}
// Update any health indicators
if (data.performance) {
updateHealthDisplay(data.performance);
}
} catch (error) {
console.error('[Socket] Error updating performance indicators:', error);
}
}
function updateStatsDisplay(stats) {
// Update various stats in the UI
try {
const statusText = document.getElementById('dashboard-status-text');
if (statusText && stats.totalGuests !== undefined) {
const runningText = stats.runningGuests ? `${stats.runningGuests} running` : '0 running';
const stoppedText = stats.stoppedGuests ? `${stats.stoppedGuests} stopped` : '0 stopped';
statusText.textContent = `${stats.totalGuests} total guests (${runningText}, ${stoppedText})`;
}
} catch (error) {
console.error('[Socket] Error updating stats display:', error);
}
}
function updateHealthDisplay(performance) {
// Update health indicators in the UI
try {
// Could add health indicators to the header or other parts of the UI
// For now, this is just a placeholder for future enhancements
} catch (error) {
console.error('[Socket] Error updating health display:', error);
}
}
// Manual data request
function requestData() {
if (socket && isConnected) {
socket.emit('requestData');
} else {
console.warn('[Socket] Cannot request data - not connected');
}
}
// Get connection status
function getConnectionStatus() {
return {
connected: isConnected,
reconnectAttempts,
socket: socket ? socket.id : null
};
}
// Manual reconnect
function reconnect() {
if (socket) {
reconnectAttempts = 0;
socket.disconnect();
socket.connect();
}
}
// Cleanup
function destroy() {
if (socket) {
socket.removeAllListeners();
socket.disconnect();
socket = null;
}
isConnected = false;
window.socket = null;
}
// Public API
return {
init,
requestData,
getConnectionStatus,
reconnect,
destroy
};
})();
// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', PulseApp.socketHandler.init);
} else {
PulseApp.socketHandler.init();
}