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;