diff --git a/build-linux-x64.sh b/build-linux-x64.sh new file mode 100755 index 0000000..dac19ef --- /dev/null +++ b/build-linux-x64.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Script de build pour Linux AMD64 depuis Mac ARM64 +# Ce script utilise electron-builder pour créer un package Linux x64 + +echo "🚀 Build SimpleConnect pour Linux AMD64" +echo "=========================================" + +# Vérifier que nous sommes dans le bon répertoire +if [ ! -f "package.json" ]; then + echo "❌ Erreur: package.json non trouvé. Exécutez ce script depuis la racine du projet." + exit 1 +fi + +# Nettoyer les builds précédents +echo "🧹 Nettoyage des builds précédents..." +rm -rf dist/ + +# Installer les dépendances si nécessaire +echo "📦 Vérification des dépendances..." +if [ ! -d "node_modules" ]; then + echo "Installation des dépendances..." + npm install +fi + +# Configuration pour le cross-compilation +export ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=true + +# Build pour Linux x64 +echo "🔨 Build en cours pour Linux x64..." +echo "Note: Le build cross-platform peut prendre quelques minutes..." + +# Utiliser npx pour garantir l'utilisation de electron-builder local +npx electron-builder --linux --x64 + +# Vérifier le succès du build +if [ $? -eq 0 ]; then + echo "" + echo "✅ Build terminé avec succès!" + echo "" + echo "📦 Fichiers générés dans ./dist/ :" + echo "=========================================" + ls -lh dist/*.AppImage 2>/dev/null && echo " ✓ AppImage (format universel)" + ls -lh dist/*.deb 2>/dev/null && echo " ✓ Package Debian/Ubuntu" + ls -lh dist/*.rpm 2>/dev/null && echo " ✓ Package RedHat/Fedora" + echo "" + echo "📋 Instructions de déploiement:" + echo "1. Copier le fichier approprié sur la machine Linux cible" + echo "2. Pour AppImage: chmod +x SimpleConnect-*.AppImage && ./SimpleConnect-*.AppImage" + echo "3. Pour .deb: sudo dpkg -i simpleconnect-electron_*.deb" + echo "4. Pour .rpm: sudo rpm -i simpleconnect-electron-*.rpm" +else + echo "" + echo "❌ Erreur lors du build" + echo "Vérifiez les messages d'erreur ci-dessus" + exit 1 +fi \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md index fee4669..a5e4347 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,47 @@ # Changelog - SimpleConnect Electron +## [1.2.16] - 2025-09-05 + +### Ajouté +- **Système de logging SignalR complet** : Capture et analyse de tous les événements + - Fichier de log centralisé dans `~/.simpleconnect-ng/signalr.log` + - Logger universel pour tous les messages SignalR reçus + - Écoute de 13 types d'événements potentiels (IpbxEvent, AgentStatusChanged, QueueUpdate, etc.) + - Format JSON structuré avec timestamp, arguments et contexte agent + - Logs des méthodes invoquées (AgentLogin, AgentLogoff, GetTerminalListByServiceProvider) + - Identification des codes IPBX 0-5 avec descriptions détaillées + +### Corrigé +- **Icônes manquantes sur Linux** : Remplacement des emojis par des SVG + - Icônes SVG inline pour les boutons Rafraîchir et Notes + - Compatibilité universelle (Windows, Mac, Linux) + - Style adaptatif suivant le thème (currentColor) + - Animations au survol et lors des actions + +- **Barre de menu Electron** : Suppression complète sur tous les OS + - Ajout de `autoHideMenuBar: true` dans BrowserWindow + - `setMenuBarVisibility(false)` pour forcer la suppression + - `Menu.setApplicationMenu(null)` pour suppression globale + - Interface épurée sans menu "File, Edit, View, Window, Help" + +### Modifié +- **Configuration de build Linux** : Support multi-architectures + - Ajout des cibles AppImage, .deb et .rpm + - Support x64 et arm64 + - Scripts npm dédiés : `build:linux-x64` et `build:linux-arm64` + - Métadonnées Linux enrichies (maintainer, vendor, synopsis) + +### Technique +- Module `os` ajouté pour accès au répertoire home utilisateur +- Fonctions de logging : `ensureLogDirectory()`, `logToSignalRFile()`, `logSignalR()` +- CSS pour icônes SVG avec transitions et animations +- Build cross-platform depuis Mac M1 vers Linux AMD64 + +### Documentation +- Instructions complètes pour le build Linux +- Guide d'utilisation des fichiers AppImage, .deb et .rpm +- Explication du poids des fichiers AppImage (106 MB) + ## [1.2.15] - 2025-09-04 ### Corrigé diff --git a/index.html b/index.html index 66334a4..b52b995 100644 --- a/index.html +++ b/index.html @@ -85,10 +85,20 @@ Disponible diff --git a/main.js b/main.js index 62846be..b962570 100644 --- a/main.js +++ b/main.js @@ -1,6 +1,7 @@ const { app, BrowserWindow, ipcMain, session } = require('electron'); const path = require('path'); const fs = require('fs'); +const os = require('os'); const signalR = require('@microsoft/signalr'); let mainWindow; @@ -11,6 +12,41 @@ let signalRConnection = null; let signalRStatus = 'disconnected'; // disconnected, connecting, connected, error let agentConnectionInfo = null; // Informations complètes retournées par SignalR +// Configuration du système de logs SignalR +const SIGNALR_LOG_DIR = path.join(os.homedir(), '.simpleconnect-ng'); +const SIGNALR_LOG_FILE = path.join(SIGNALR_LOG_DIR, 'signalr.log'); + +// Créer le répertoire de logs s'il n'existe pas +function ensureLogDirectory() { + if (!fs.existsSync(SIGNALR_LOG_DIR)) { + fs.mkdirSync(SIGNALR_LOG_DIR, { recursive: true }); + console.log(`📁 Répertoire de logs créé: ${SIGNALR_LOG_DIR}`); + } +} + +// Fonction pour écrire dans le fichier de log SignalR +function logToSignalRFile(message, data = null) { + ensureLogDirectory(); + + const timestamp = new Date().toISOString(); + let logEntry = `[${timestamp}] ${message}`; + + if (data) { + logEntry += '\n' + JSON.stringify(data, null, 2); + } + + logEntry += '\n' + '─'.repeat(80) + '\n'; + + // Ajouter au fichier (append) + fs.appendFileSync(SIGNALR_LOG_FILE, logEntry, 'utf8'); +} + +// Logger dans la console ET dans le fichier +function logSignalR(message, data = null) { + console.log(message, data || ''); + logToSignalRFile(message, data); +} + // Charger la configuration function loadConfig() { const configPath = path.join(__dirname, 'config.json'); @@ -30,9 +66,13 @@ function createWindow() { webSecurity: false }, icon: path.join(__dirname, 'icon.png'), - title: 'SimpleConnect' + title: 'SimpleConnect', + autoHideMenuBar: true // Cache la barre de menu par défaut }); + // Supprimer complètement le menu (pour Linux/Windows) + mainWindow.setMenuBarVisibility(false); + // Charger l'interface HTML mainWindow.loadFile('index.html'); @@ -67,18 +107,30 @@ function initializeSignalR() { // Gérer les changements d'état signalRConnection.onreconnecting(() => { console.log('SignalR: Reconnexion en cours...'); + logSignalR('🔄 SignalR en reconnexion...', { + previousStatus: signalRStatus, + timestamp: new Date().toISOString() + }); signalRStatus = 'connecting'; sendSignalRStatus(); }); signalRConnection.onreconnected(() => { console.log('SignalR: Reconnecté'); + logSignalR('🔗 SignalR reconnecté avec succès', { + connectionId: signalRConnection.connectionId, + timestamp: new Date().toISOString() + }); signalRStatus = 'connected'; sendSignalRStatus(); }); signalRConnection.onclose(() => { console.log('SignalR: Connexion fermée'); + logSignalR('🔌 SignalR déconnecté', { + lastConnectionId: signalRConnection.connectionId, + timestamp: new Date().toISOString() + }); signalRStatus = 'disconnected'; sendSignalRStatus(); }); @@ -97,35 +149,136 @@ function initializeSignalR() { } function setupSignalRMethods() { - // Écouter les événements IPBX - signalRConnection.on('IpbxEvent', (name, args) => { + // === LOGGER UNIVERSEL POUR TOUS LES MESSAGES SIGNALR === + // Intercepter TOUS les messages reçus du serveur pour découvrir les événements disponibles + + // Initialiser le fichier de log avec une session + logSignalR('════════════════════════════════════════════════════════════'); + logSignalR('🚀 NOUVELLE SESSION SIGNALR DÉMARRÉE'); + logSignalR(`Application: SimpleConnect v${app.getVersion()}`); + logSignalR(`Serveur SignalR: ${config.signalR.serverUrl}`); + logSignalR(`Service Provider: ${config.signalR.serviceProvider}`); + logSignalR('════════════════════════════════════════════════════════════'); + + // Liste des événements connus pour les logger différemment + const knownEvents = ['IpbxEvent']; + + // Créer un proxy pour intercepter tous les appels .on() + const originalOn = signalRConnection.on.bind(signalRConnection); + + // Logger tous les événements possibles en essayant d'écouter les plus communs + const possibleEvents = [ + 'IpbxEvent', // Événements téléphoniques (confirmé) + 'AgentStatusChanged', // Changement de statut agent + 'QueueUpdate', // Mise à jour des files + 'CallReceived', // Appel entrant + 'CallEnded', // Fin d'appel + 'MessageReceived', // Messages + 'Notification', // Notifications générales + 'StatusUpdate', // Mises à jour de statut + 'SystemMessage', // Messages système + 'BroadcastMessage', // Messages broadcast + 'AgentUpdate', // Mises à jour agent + 'QueueStatistics', // Statistiques de file + 'PresenceUpdate' // Mise à jour de présence + ]; + + // Écouter tous les événements possibles et logger ce qu'on reçoit + possibleEvents.forEach(eventName => { + signalRConnection.on(eventName, (...args) => { + // Logger dans la console avec formatage + console.log('═══════════════════════════════════════════════════════════'); + console.log(`📨 MESSAGE SIGNALR REÇU: ${eventName}`); + console.log('Timestamp:', new Date().toISOString()); + console.log('Nombre d\'arguments:', args.length); + + // Logger chaque argument en détail + args.forEach((arg, index) => { + console.log(`Argument ${index}:`, JSON.stringify(arg, null, 2)); + }); + + console.log('═══════════════════════════════════════════════════════════'); + + // Logger dans le fichier avec structure + const logData = { + event: eventName, + timestamp: new Date().toISOString(), + argumentCount: args.length, + arguments: args.map((arg, index) => ({ + index: index, + type: typeof arg, + value: arg + })), + agent: currentAgent ? { + id: currentAgent.id, + name: currentAgent.name, + terminal: currentTerminal + } : null + }; + + logSignalR(`📨 MESSAGE SIGNALR REÇU: ${eventName}`, logData); + + // Si c'est IpbxEvent, traiter comme avant + if (eventName === 'IpbxEvent') { + handleIpbxEventOriginal(args); + } + }); + }); + + // Fonction originale pour traiter les IpbxEvent + function handleIpbxEventOriginal(args) { if (!args || !agentConnectionInfo) return; - const event = args[0]; - console.log('Événement IPBX reçu:', { + const [name, eventArgs] = args; + const event = eventArgs?.[0] || args[0]; + + console.log('🔍 Traitement IpbxEvent:', { eventCode: event.eventCode, terminal: event.terminal, - queueName: event.queueName + queueName: event.queueName, + fullEvent: event // Logger l'objet complet pour voir tous les champs }); // Vérifier que l'événement est pour notre terminal if (event.terminal !== currentTerminal) { - console.log('Événement ignoré - Terminal différent:', event.terminal, '!==', currentTerminal); + console.log('⚠️ Événement ignoré - Terminal différent:', event.terminal, '!==', currentTerminal); return; } // Gérer les différents types d'événements switch(event.eventCode) { + case 0: + console.log('📞 Code 0: Appel entrant/sonnerie (non implémenté)'); + break; case 1: // Appel décroché + console.log('✅ Code 1: Appel décroché - Traitement...'); handleCallPickedUp(event); break; - case 2: // Appel raccroché + case 2: // Appel raccroché + console.log('📴 Code 2: Appel raccroché - Traitement...'); handleCallHungUp(event); break; + case 3: + console.log('⏸️ Code 3: Mise en attente (non implémenté)'); + break; + case 4: + console.log('↔️ Code 4: Transfert d\'appel (non implémenté)'); + break; + case 5: + console.log('👥 Code 5: Conférence (non implémenté)'); + break; default: - console.log('Code événement non géré:', event.eventCode); + console.log('❓ Code événement inconnu:', event.eventCode); + console.log('Données complètes:', JSON.stringify(event, null, 2)); } - }); + } + + // Logger aussi les méthodes qu'on peut invoquer + console.log('📋 MÉTHODES SIGNALR DISPONIBLES POUR INVOCATION:'); + console.log('- AgentLogin(email, password, terminal)'); + console.log('- AgentLogoff(accessCode)'); + console.log('- GetTerminalListByServiceProvider(serviceProvider)'); + console.log('ℹ️ D\'autres méthodes peuvent être disponibles sur le serveur'); } // Gérer un appel entrant @@ -170,14 +323,28 @@ async function startSignalRConnection() { try { signalRStatus = 'connecting'; sendSignalRStatus(); + logSignalR('🔌 Tentative de connexion SignalR...', { + serverUrl: config.signalR.serverUrl, + status: 'connecting' + }); await signalRConnection.start(); console.log('SignalR: Connexion établie'); + logSignalR('✅ Connexion SignalR établie avec succès', { + connectionId: signalRConnection.connectionId, + status: 'connected', + serverUrl: config.signalR.serverUrl + }); signalRStatus = 'connected'; sendSignalRStatus(); } catch (error) { console.error('Erreur connexion SignalR:', error); + logSignalR('❌ Erreur de connexion SignalR', { + error: error.message, + stack: error.stack, + serverUrl: config.signalR.serverUrl + }); signalRStatus = 'error'; sendSignalRStatus(); @@ -198,6 +365,10 @@ function sendSignalRStatus() { // Initialisation de l'application app.whenReady().then(() => { + // Supprimer le menu de l'application complètement (toutes plateformes) + const { Menu } = require('electron'); + Menu.setApplicationMenu(null); + // Configuration de la session pour éviter les problèmes CORS session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { details.requestHeaders['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; @@ -271,12 +442,21 @@ ipcMain.handle('get-terminal-list', async () => { try { console.log('Récupération des terminaux pour:', config.signalR.serviceProvider); + logSignalR('📡 Invocation SignalR: GetTerminalListByServiceProvider', { + method: 'GetTerminalListByServiceProvider', + serviceProvider: config.signalR.serviceProvider + }); + const terminals = await signalRConnection.invoke( 'GetTerminalListByServiceProvider', config.signalR.serviceProvider ); console.log('Terminaux disponibles:', terminals); + logSignalR('📞 Terminaux récupérés', { + count: terminals.length, + terminals: terminals + }); return terminals || []; } catch (error) { console.error('Erreur récupération terminaux:', error); @@ -297,6 +477,11 @@ ipcMain.handle('login-agent', async (event, credentials) => { try { console.log('Tentative de connexion agent:', credentials.email, 'Terminal:', credentials.terminal); + logSignalR('🔐 Tentative de connexion agent', { + email: credentials.email, + terminal: credentials.terminal, + forceDisconnect: credentials.forceDisconnect || false + }); // Si déconnexion forcée demandée, déconnecter d'abord la session précédente if (credentials.forceDisconnect) { @@ -305,13 +490,26 @@ ipcMain.handle('login-agent', async (event, credentials) => { // Tenter la déconnexion avec le code d'accès await signalRConnection.invoke('AgentLogoff', credentials.email); console.log('Session précédente déconnectée avec succès'); + logSignalR('🔓 Session précédente déconnectée (forceDisconnect)', { + email: credentials.email + }); } catch (logoffError) { console.warn('Erreur lors de la déconnexion forcée (session peut-être déjà fermée):', logoffError.message); + logSignalR('⚠️ Erreur déconnexion forcée', { + email: credentials.email, + error: logoffError.message + }); // Continuer même si la déconnexion échoue - la session est peut-être déjà fermée } } // Appel SignalR pour l'authentification + logSignalR('📡 Invocation SignalR: AgentLogin', { + method: 'AgentLogin', + email: credentials.email, + terminal: credentials.terminal + }); + const result = await signalRConnection.invoke('AgentLogin', credentials.email, credentials.password, @@ -320,6 +518,14 @@ ipcMain.handle('login-agent', async (event, credentials) => { if (result) { console.log('Connexion réussie:', result); + logSignalR('✅ Connexion agent réussie', { + accessCode: result.accessCode, + firstName: result.firstName, + lastName: result.lastName, + terminal: credentials.terminal, + connListCount: result.connList ? result.connList.length : 0, + connList: result.connList + }); // Stocker les informations de connexion agentConnectionInfo = result; @@ -419,8 +625,16 @@ ipcMain.handle('logout', async () => { if (currentAgent && signalRConnection && signalRStatus === 'connected') { try { // Appeler SignalR pour la déconnexion + logSignalR('📡 Invocation SignalR: AgentLogoff', { + method: 'AgentLogoff', + accessCode: currentAgent.accessCode + }); await signalRConnection.invoke('AgentLogoff', currentAgent.accessCode); console.log('Agent déconnecté du serveur SignalR'); + logSignalR('👋 Agent déconnecté avec succès', { + accessCode: currentAgent.accessCode, + name: currentAgent.name + }); } catch (error) { console.error('Erreur lors de la déconnexion SignalR:', error); } diff --git a/package.json b/package.json index d8f11d7..fe30426 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simpleconnect-electron", - "version": "1.0.0", + "version": "1.2.16", "description": "Application de gestion centralisée des plannings médicaux pour centres d'appels", "main": "main.js", "scripts": { @@ -10,6 +10,8 @@ "build:win": "electron-builder --win", "build:mac": "electron-builder --mac", "build:linux": "electron-builder --linux", + "build:linux-x64": "electron-builder --linux --x64", + "build:linux-arm64": "electron-builder --linux --arm64", "dist": "electron-builder", "postinstall": "electron-builder install-app-deps" }, @@ -48,9 +50,31 @@ "icon": "icon.ico" }, "linux": { - "target": "AppImage", + "target": [ + { + "target": "AppImage", + "arch": ["x64", "arm64"] + }, + { + "target": "deb", + "arch": ["x64", "arm64"] + }, + { + "target": "rpm", + "arch": ["x64", "arm64"] + } + ], "icon": "icon.png", - "category": "Office" + "category": "Office", + "maintainer": "SimpleConnect Team", + "vendor": "SimpleConnect", + "synopsis": "Gestion centralisée des plannings médicaux", + "description": "Application de gestion centralisée des plannings médicaux pour centres d'appels avec intégration CTI", + "desktop": { + "StartupNotify": "true", + "Encoding": "UTF-8", + "MimeType": "x-scheme-handler/simpleconnect" + } }, "nsis": { "oneClick": false, diff --git a/styles-modern.css b/styles-modern.css index 51739ea..ad1c564 100644 --- a/styles-modern.css +++ b/styles-modern.css @@ -404,6 +404,28 @@ body { border-color: #667eea; } +/* Style pour les icônes SVG */ +.btn-icon svg.icon { + width: 20px; + height: 20px; + stroke: currentColor; + stroke-width: 2; + fill: none; + transition: transform 0.2s ease; +} + +.btn-icon:hover svg.icon { + transform: scale(1.1); +} + +.btn-icon.active svg.icon { + stroke: white; +} + +.btn-icon.rotating svg.icon { + animation: rotate 1s linear infinite; +} + .btn-icon.rotating .icon-refresh { animation: rotate 1s linear; display: inline-block;