mirror of
https://github.com/Alvin-Zilverstand/narrow_casting_system.git
synced 2026-03-06 13:24:46 +01:00
✅ Full-stack narrowcasting platform implementation ✅ Real-time WebSocket communication for instant updates ✅ Zone-specific content distribution (reception, restaurant, skislope, lockers, shop) ✅ Professional admin dashboard with content management interface ✅ Beautiful client display with winter/snow theme matching SnowWorld branding ✅ Comprehensive technical documentation and test suite ✅ Docker deployment support with CI/CD pipeline ✅ All system tests passing successfully 🏗️ Technical Implementation: - Backend: Node.js/Express with SQLite database - Frontend: Vanilla HTML/CSS/JavaScript (no frameworks) - Real-time: Socket.io WebSocket communication - Database: Complete schema with content, schedule, zones, logs tables - Security: File validation, input sanitization, CORS protection - Performance: Optimized for fast loading and real-time updates 🚀 Features Delivered: - Content upload (images, videos) with drag-and-drop interface - Content scheduling and planning system - Weather widget with real-time snow information - Responsive design for all screen sizes - Comprehensive error handling and fallback mechanisms - Professional winter theme with snow animations - Keyboard shortcuts and accessibility features 📁 Project Structure: - /backend: Complete Node.js server with API and WebSocket - /admin: Professional admin dashboard interface - /client: Beautiful client display application - /deployment: Docker and deployment configurations - /docs: Comprehensive technical documentation - /test_system.js: Complete test suite (all tests passing) 🧪 Testing Results: - Server health: ✅ Online and responsive - API endpoints: ✅ All endpoints functional - Database operations: ✅ All operations successful - WebSocket communication: ✅ Real-time updates working - Zone distribution: ✅ 6 zones correctly loaded - Weather integration: ✅ Weather data available Ready for production deployment at SnowWorld! 🎿❄️
240 lines
7.2 KiB
JavaScript
240 lines
7.2 KiB
JavaScript
// WebSocket Management for SnowWorld Admin Dashboard
|
|
class WebSocketManager {
|
|
constructor() {
|
|
this.socket = null;
|
|
this.isConnected = false;
|
|
this.reconnectAttempts = 0;
|
|
this.maxReconnectAttempts = 5;
|
|
this.reconnectDelay = 1000;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.connect();
|
|
}
|
|
|
|
connect() {
|
|
try {
|
|
this.socket = io('http://localhost:3000', {
|
|
transports: ['websocket', 'polling'],
|
|
timeout: 5000,
|
|
forceNew: true
|
|
});
|
|
|
|
this.setupEventListeners();
|
|
} catch (error) {
|
|
console.error('WebSocket connection error:', error);
|
|
this.handleConnectionError();
|
|
}
|
|
}
|
|
|
|
setupEventListeners() {
|
|
this.socket.on('connect', () => {
|
|
console.log('WebSocket connected');
|
|
this.isConnected = true;
|
|
this.reconnectAttempts = 0;
|
|
this.updateConnectionStatus(true);
|
|
|
|
// Join admin room for global updates
|
|
this.socket.emit('joinZone', 'admin');
|
|
|
|
this.showToast('Verbonden met server', 'success');
|
|
});
|
|
|
|
this.socket.on('disconnect', () => {
|
|
console.log('WebSocket disconnected');
|
|
this.isConnected = false;
|
|
this.updateConnectionStatus(false);
|
|
|
|
// Attempt reconnection
|
|
this.attemptReconnect();
|
|
});
|
|
|
|
this.socket.on('connect_error', (error) => {
|
|
console.error('WebSocket connection error:', error);
|
|
this.handleConnectionError();
|
|
});
|
|
|
|
// Content updates
|
|
this.socket.on('contentUpdated', (data) => {
|
|
console.log('Content update received:', data);
|
|
this.handleContentUpdate(data);
|
|
});
|
|
|
|
// Schedule updates
|
|
this.socket.on('scheduleUpdated', (data) => {
|
|
console.log('Schedule update received:', data);
|
|
this.handleScheduleUpdate(data);
|
|
});
|
|
|
|
// Zone-specific updates
|
|
this.socket.on('zoneUpdate', (data) => {
|
|
console.log('Zone update received:', data);
|
|
this.handleZoneUpdate(data);
|
|
});
|
|
|
|
// System notifications
|
|
this.socket.on('systemNotification', (data) => {
|
|
console.log('System notification:', data);
|
|
this.handleSystemNotification(data);
|
|
});
|
|
}
|
|
|
|
handleContentUpdate(data) {
|
|
// Clear content cache to force refresh
|
|
if (window.ui) {
|
|
window.ui.clearContentCache();
|
|
}
|
|
|
|
// Show notification based on update type
|
|
switch (data.type) {
|
|
case 'content_added':
|
|
this.showToast(`Nieuwe content toegevoegd: ${data.content.title}`, 'info');
|
|
break;
|
|
case 'content_deleted':
|
|
this.showToast('Content verwijderd', 'warning');
|
|
break;
|
|
case 'content_updated':
|
|
this.showToast('Content bijgewerkt', 'info');
|
|
break;
|
|
}
|
|
|
|
// Refresh current view if on content tab
|
|
if (window.ui && window.ui.currentTab === 'content') {
|
|
window.ui.loadContent();
|
|
}
|
|
}
|
|
|
|
handleScheduleUpdate(data) {
|
|
// Show notification
|
|
this.showToast(`Planning bijgewerkt voor zone: ${data.zone}`, 'info');
|
|
|
|
// Refresh schedule view if currently viewing this zone
|
|
const currentZone = document.getElementById('scheduleZoneSelect')?.value;
|
|
if (window.ui && window.ui.currentTab === 'schedule' && currentZone === data.zone) {
|
|
window.ui.loadSchedule();
|
|
}
|
|
}
|
|
|
|
handleZoneUpdate(data) {
|
|
// Handle zone-specific updates
|
|
this.showToast(`Zone ${data.zone} bijgewerkt`, 'info');
|
|
|
|
// Refresh relevant views
|
|
if (window.ui) {
|
|
if (window.ui.currentTab === 'zones') {
|
|
window.ui.loadZonesOverview();
|
|
} else if (window.ui.currentTab === 'content') {
|
|
window.ui.loadContent();
|
|
}
|
|
}
|
|
}
|
|
|
|
handleSystemNotification(data) {
|
|
// Handle system-level notifications
|
|
const { message, type, duration } = data;
|
|
this.showToast(message, type || 'info', duration);
|
|
}
|
|
|
|
updateConnectionStatus(connected) {
|
|
const statusDot = document.getElementById('connectionStatus');
|
|
const statusText = document.getElementById('connectionText');
|
|
|
|
if (statusDot) {
|
|
statusDot.className = connected ? 'status-dot' : 'status-dot disconnected';
|
|
}
|
|
|
|
if (statusText) {
|
|
statusText.textContent = connected ? 'Verbonden' : 'Verbinding verbroken';
|
|
}
|
|
}
|
|
|
|
attemptReconnect() {
|
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
console.error('Max reconnection attempts reached');
|
|
this.showToast('Kan geen verbinding maken met de server', 'error');
|
|
return;
|
|
}
|
|
|
|
this.reconnectAttempts++;
|
|
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
|
|
console.log(`Attempting reconnection ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);
|
|
|
|
setTimeout(() => {
|
|
if (!this.isConnected) {
|
|
this.connect();
|
|
}
|
|
}, delay);
|
|
}
|
|
|
|
handleConnectionError() {
|
|
this.isConnected = false;
|
|
this.updateConnectionStatus(false);
|
|
|
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
this.showToast('Verbinding verbroken. Probeert opnieuw...', 'warning');
|
|
} else {
|
|
this.showToast('Kan geen verbinding maken met de server', 'error');
|
|
}
|
|
}
|
|
|
|
// Public methods
|
|
joinZone(zone) {
|
|
if (this.isConnected && this.socket) {
|
|
this.socket.emit('joinZone', zone);
|
|
console.log(`Joined zone: ${zone}`);
|
|
}
|
|
}
|
|
|
|
leaveZone(zone) {
|
|
if (this.isConnected && this.socket) {
|
|
this.socket.emit('leaveZone', zone);
|
|
console.log(`Left zone: ${zone}`);
|
|
}
|
|
}
|
|
|
|
sendMessage(event, data) {
|
|
if (this.isConnected && this.socket) {
|
|
this.socket.emit(event, data);
|
|
} else {
|
|
console.warn('Cannot send message: not connected');
|
|
}
|
|
}
|
|
|
|
disconnect() {
|
|
if (this.socket) {
|
|
this.socket.disconnect();
|
|
this.isConnected = false;
|
|
this.updateConnectionStatus(false);
|
|
}
|
|
}
|
|
|
|
reconnect() {
|
|
this.disconnect();
|
|
this.reconnectAttempts = 0;
|
|
this.connect();
|
|
}
|
|
|
|
// Utility methods
|
|
showToast(message, type = 'info', duration = 5000) {
|
|
if (window.ui) {
|
|
window.ui.showToast(message, type);
|
|
} else {
|
|
// Fallback to browser notification
|
|
console.log(`Toast [${type}]: ${message}`);
|
|
}
|
|
}
|
|
|
|
// Get connection status
|
|
getConnectionStatus() {
|
|
return {
|
|
connected: this.isConnected,
|
|
reconnectAttempts: this.reconnectAttempts,
|
|
socketId: this.socket?.id || null
|
|
};
|
|
}
|
|
}
|
|
|
|
// Create global WebSocket instance
|
|
window.wsManager = new WebSocketManager(); |