mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-19 06:53:14 +02:00
Implementing WebSocked Support for immediately ban-messages
This commit is contained in:
205
pkg/web/static/js/websocket.js
Normal file
205
pkg/web/static/js/websocket.js
Normal file
@@ -0,0 +1,205 @@
|
||||
// WebSocket Manager for Fail2ban UI
|
||||
// Handles real-time communication with the backend
|
||||
|
||||
"use strict";
|
||||
|
||||
class WebSocketManager {
|
||||
constructor() {
|
||||
this.ws = null;
|
||||
this.reconnectAttempts = 0;
|
||||
this.maxReconnectAttempts = Infinity;
|
||||
this.reconnectDelay = 1000; // Start with 1 second
|
||||
this.maxReconnectDelay = 30000; // Max 30 seconds
|
||||
this.isConnecting = false;
|
||||
this.isConnected = false;
|
||||
this.lastBanEventId = null;
|
||||
this.statusCallbacks = [];
|
||||
this.banEventCallbacks = [];
|
||||
|
||||
// Get WebSocket URL
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const host = window.location.host;
|
||||
this.wsUrl = `${protocol}//${host}/api/ws`;
|
||||
|
||||
this.connect();
|
||||
}
|
||||
|
||||
connect() {
|
||||
if (this.isConnecting || (this.ws && this.ws.readyState === WebSocket.OPEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isConnecting = true;
|
||||
this.updateStatus('connecting', 'Connecting...');
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(this.wsUrl);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
this.isConnecting = false;
|
||||
this.isConnected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.reconnectDelay = 1000;
|
||||
this.updateStatus('connected', 'Connected');
|
||||
console.log('WebSocket connected');
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
this.handleMessage(message);
|
||||
} catch (err) {
|
||||
console.error('Error parsing WebSocket message:', err);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
this.updateStatus('error', 'Connection error');
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
this.isConnecting = false;
|
||||
this.isConnected = false;
|
||||
this.updateStatus('disconnected', 'Disconnected');
|
||||
console.log('WebSocket disconnected');
|
||||
|
||||
// Attempt to reconnect
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating WebSocket connection:', error);
|
||||
this.isConnecting = false;
|
||||
this.updateStatus('error', 'Connection failed');
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
scheduleReconnect() {
|
||||
this.reconnectAttempts++;
|
||||
const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
|
||||
|
||||
this.updateStatus('reconnecting', 'Reconnecting...');
|
||||
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
handleMessage(message) {
|
||||
switch (message.type) {
|
||||
case 'ban_event':
|
||||
this.handleBanEvent(message.data);
|
||||
break;
|
||||
case 'heartbeat':
|
||||
this.handleHeartbeat(message);
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown message type:', message.type);
|
||||
}
|
||||
}
|
||||
|
||||
handleBanEvent(eventData) {
|
||||
// Check if we've already processed this event (prevent duplicates)
|
||||
// Only check if event has an ID and we have a lastBanEventId
|
||||
if (eventData.id && this.lastBanEventId !== null && eventData.id <= this.lastBanEventId) {
|
||||
console.log('Skipping duplicate ban event:', eventData.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update lastBanEventId if event has an ID
|
||||
if (eventData.id) {
|
||||
if (this.lastBanEventId === null || eventData.id > this.lastBanEventId) {
|
||||
this.lastBanEventId = eventData.id;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Processing ban event:', eventData);
|
||||
|
||||
// Notify all registered callbacks
|
||||
this.banEventCallbacks.forEach(callback => {
|
||||
try {
|
||||
callback(eventData);
|
||||
} catch (err) {
|
||||
console.error('Error in ban event callback:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleHeartbeat(message) {
|
||||
// Update status to show backend is healthy
|
||||
if (this.isConnected) {
|
||||
this.updateStatus('connected', 'Connected');
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus(state, text) {
|
||||
this.statusCallbacks.forEach(callback => {
|
||||
try {
|
||||
callback(state, text);
|
||||
} catch (err) {
|
||||
console.error('Error in status callback:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onStatusChange(callback) {
|
||||
this.statusCallbacks.push(callback);
|
||||
}
|
||||
|
||||
onBanEvent(callback) {
|
||||
this.banEventCallbacks.push(callback);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
this.isConnected = false;
|
||||
this.isConnecting = false;
|
||||
}
|
||||
|
||||
getConnectionState() {
|
||||
if (!this.ws) {
|
||||
return 'disconnected';
|
||||
}
|
||||
|
||||
switch (this.ws.readyState) {
|
||||
case WebSocket.CONNECTING:
|
||||
return 'connecting';
|
||||
case WebSocket.OPEN:
|
||||
return 'connected';
|
||||
case WebSocket.CLOSING:
|
||||
return 'disconnecting';
|
||||
case WebSocket.CLOSED:
|
||||
return 'disconnected';
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
isHealthy() {
|
||||
return this.isConnected && this.ws && this.ws.readyState === WebSocket.OPEN;
|
||||
}
|
||||
}
|
||||
|
||||
// Create global instance - initialize immediately
|
||||
var wsManager = null;
|
||||
|
||||
// Initialize WebSocket manager
|
||||
if (typeof window !== 'undefined') {
|
||||
// Initialize immediately if DOM is already loaded, otherwise wait
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (!wsManager) {
|
||||
wsManager = new WebSocketManager();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
wsManager = new WebSocketManager();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user