feat: logs structurés avec couleurs ANSI et format lisible
- Banner au démarrage avec version et URL serveur - Pastilles ✓/✗ pour connexion/déconnexion serveur - Terminaux récupérés fusionnés dans la ligne de connexion - Timestamps hh:mm:ss en console, date complète dans le fichier log - Strip ANSI dans le fichier log pour lisibilité SSH - Log de fermeture avant app.quit() - Terminaux chargés uniquement quand le serveur est joignable
This commit is contained in:
160
main.js
160
main.js
@@ -23,26 +23,60 @@ function ensureLogDirectory() {
|
||||
}
|
||||
}
|
||||
|
||||
function stripAnsi(str) {
|
||||
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
}
|
||||
|
||||
function fileTimestamp() {
|
||||
return new Date().toLocaleString('fr-FR', {
|
||||
timeZone: 'Europe/Paris',
|
||||
day: '2-digit', month: '2-digit', year: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
function logToFile(message, data = null) {
|
||||
ensureLogDirectory();
|
||||
let line = `[${fileTimestamp()}] ${stripAnsi(message)}`;
|
||||
if (data) line += ' ' + JSON.stringify(data);
|
||||
fs.appendFileSync(LOG_FILE, line + '\n', 'utf8');
|
||||
}
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
let logEntry = `[${timestamp}] ${message}`;
|
||||
function logSectionToFile(message) {
|
||||
ensureLogDirectory();
|
||||
fs.appendFileSync(LOG_FILE, `[${fileTimestamp()}] ── ${stripAnsi(message)} ──\n`, 'utf8');
|
||||
}
|
||||
|
||||
if (data) {
|
||||
logEntry += '\n' + JSON.stringify(data, null, 2);
|
||||
}
|
||||
// ANSI colors
|
||||
const c = {
|
||||
reset: '\x1b[0m',
|
||||
dim: '\x1b[2m',
|
||||
green: '\x1b[32m',
|
||||
red: '\x1b[31m',
|
||||
cyan: '\x1b[36m',
|
||||
bold: '\x1b[1m',
|
||||
};
|
||||
|
||||
logEntry += '\n' + '-'.repeat(80) + '\n';
|
||||
|
||||
fs.appendFileSync(LOG_FILE, logEntry, 'utf8');
|
||||
function formatTimestamp() {
|
||||
return new Date().toLocaleString('fr-FR', {
|
||||
timeZone: 'Europe/Paris',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
function log(message, data = null) {
|
||||
console.log(message, data || '');
|
||||
console.log(`${c.dim}${formatTimestamp()}${c.reset} ${message}`);
|
||||
logToFile(message, data);
|
||||
}
|
||||
|
||||
function logBanner(version, serverUrl) {
|
||||
console.log('');
|
||||
console.log(` ${c.bold}${c.cyan}SimpleConnect${c.reset} ${c.dim}v${version}${c.reset}`);
|
||||
console.log(` ${c.dim}Serveur${c.reset} ${serverUrl}`);
|
||||
console.log('');
|
||||
logSectionToFile(`SimpleConnect v${version} — ${serverUrl}`);
|
||||
}
|
||||
|
||||
// Charger la configuration
|
||||
function loadConfig() {
|
||||
const configPath = path.join(__dirname, 'config.json');
|
||||
@@ -92,49 +126,77 @@ function initializeSocketIO() {
|
||||
return;
|
||||
}
|
||||
|
||||
logBanner(app.getVersion(), config.socketio.serverUrl);
|
||||
|
||||
adapter = new SocketIOAdapter(config.socketio.serverUrl);
|
||||
|
||||
// Demarrer le health check polling
|
||||
// Demarrer le health check (ecran de login)
|
||||
startHealthCheck();
|
||||
}
|
||||
|
||||
// Health check periodique via GET /health
|
||||
// Actif uniquement sur l'ecran de login (pas d'agent connecte)
|
||||
function startHealthCheck() {
|
||||
const checkHealth = () => {
|
||||
// Ne pas faire de health check quand un agent est connecte
|
||||
// (c'est l'adapter Socket.IO qui pilote le voyant)
|
||||
if (currentAgent) return;
|
||||
|
||||
const serverUrl = config.socketio.serverUrl;
|
||||
let done = false;
|
||||
|
||||
const request = net.request(`${serverUrl}/health`);
|
||||
|
||||
request.on('response', (response) => {
|
||||
if (response.statusCode === 200) {
|
||||
if (serverStatus !== 'connected') {
|
||||
// Ne passer en 'connected' que si on n'a pas d'agent connecte
|
||||
// (sinon le status est deja 'connected' via le login)
|
||||
if (!currentAgent) {
|
||||
serverStatus = 'connected';
|
||||
sendServerStatus();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
serverStatus = 'error';
|
||||
if (done) return;
|
||||
done = true;
|
||||
const newStatus = response.statusCode === 200 ? 'connected' : 'error';
|
||||
if (serverStatus !== newStatus) {
|
||||
serverStatus = newStatus;
|
||||
sendServerStatus();
|
||||
}
|
||||
});
|
||||
|
||||
request.on('error', () => {
|
||||
serverStatus = 'error';
|
||||
sendServerStatus();
|
||||
if (done) return;
|
||||
done = true;
|
||||
if (serverStatus !== 'error') {
|
||||
serverStatus = 'error';
|
||||
sendServerStatus();
|
||||
}
|
||||
});
|
||||
|
||||
// Timeout 5s — net.request n'a pas de timeout natif
|
||||
setTimeout(() => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
request.abort();
|
||||
if (serverStatus !== 'error') {
|
||||
serverStatus = 'error';
|
||||
sendServerStatus();
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
request.end();
|
||||
};
|
||||
|
||||
// Check immediat puis toutes les 10s
|
||||
// Check immediat puis toutes les 5s
|
||||
checkHealth();
|
||||
healthCheckInterval = setInterval(checkHealth, 10000);
|
||||
healthCheckInterval = setInterval(checkHealth, 5000);
|
||||
}
|
||||
|
||||
function stopHealthCheck() {
|
||||
if (healthCheckInterval) {
|
||||
clearInterval(healthCheckInterval);
|
||||
healthCheckInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function sendServerStatus() {
|
||||
if (serverStatus !== 'connected') {
|
||||
log(`${c.red}✗${c.reset} Serveur injoignable`);
|
||||
}
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('server-status', serverStatus);
|
||||
}
|
||||
@@ -177,7 +239,7 @@ function handleCallHungUp(event) {
|
||||
function setupEventHandlers() {
|
||||
if (!adapter) return;
|
||||
|
||||
log('Session Socket.IO demarree', {
|
||||
log('Session Socket.IO démarrée', {
|
||||
serverUrl: config.socketio.serverUrl,
|
||||
serviceProvider: config.socketio.serviceProvider
|
||||
});
|
||||
@@ -237,17 +299,18 @@ app.on('window-all-closed', async () => {
|
||||
clearInterval(healthCheckInterval);
|
||||
}
|
||||
|
||||
// Deconnexion propre avant de quitter
|
||||
// Déconnexion propre avant de quitter
|
||||
if (currentAgent && adapter) {
|
||||
try {
|
||||
await adapter.logoff();
|
||||
console.log('Agent deconnecte avant fermeture');
|
||||
} catch (error) {
|
||||
console.error('Erreur deconnexion:', error);
|
||||
console.error('Erreur déconnexion:', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
console.log(`${c.dim}${formatTimestamp()}${c.reset} ${c.dim}Application fermée${c.reset}`);
|
||||
logSectionToFile('Application fermée');
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
@@ -282,8 +345,6 @@ ipcMain.handle('get-terminal-list', async () => {
|
||||
try {
|
||||
const provider = config.socketio.serviceProvider;
|
||||
const url = `${config.socketio.serverUrl}/terminals?provider=${encodeURIComponent(provider)}`;
|
||||
console.log('Recuperation des terminaux:', url);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = net.request(url);
|
||||
let body = '';
|
||||
@@ -297,7 +358,7 @@ ipcMain.handle('get-terminal-list', async () => {
|
||||
if (response.statusCode === 200) {
|
||||
try {
|
||||
const terminals = JSON.parse(body);
|
||||
log('Terminaux recuperes', { count: terminals.length, terminals });
|
||||
log(`${c.green}✓${c.reset} Serveur connecté — ${terminals.length} terminaux`);
|
||||
resolve(Array.isArray(terminals) ? terminals : []);
|
||||
} catch (e) {
|
||||
console.error('Erreur parsing terminaux:', e);
|
||||
@@ -348,6 +409,26 @@ ipcMain.handle('login-agent', async (event, credentials) => {
|
||||
// Recreer l'adapter pour une connexion fraiche
|
||||
adapter = new SocketIOAdapter(config.socketio.serverUrl);
|
||||
|
||||
// L'adapter pilote le voyant une fois connecte
|
||||
adapter.onStateChange((state) => {
|
||||
log('Etat Socket.IO change', { state });
|
||||
switch (state) {
|
||||
case 'reconnecting':
|
||||
serverStatus = 'connecting';
|
||||
sendServerStatus();
|
||||
break;
|
||||
case 'connected':
|
||||
serverStatus = 'connected';
|
||||
sendServerStatus();
|
||||
break;
|
||||
case 'error':
|
||||
case 'disconnected':
|
||||
serverStatus = 'error';
|
||||
sendServerStatus();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Configurer les handlers d'evenements AVANT connect
|
||||
setupEventHandlers();
|
||||
|
||||
@@ -359,8 +440,8 @@ ipcMain.handle('login-agent', async (event, credentials) => {
|
||||
);
|
||||
|
||||
if (result) {
|
||||
console.log('Connexion reussie:', result);
|
||||
log('Connexion agent reussie', {
|
||||
console.log('Connexion réussie:', result);
|
||||
log('Connexion agent réussie', {
|
||||
accessCode: result.accessCode,
|
||||
firstName: result.firstName,
|
||||
lastName: result.lastName,
|
||||
@@ -390,6 +471,8 @@ ipcMain.handle('login-agent', async (event, credentials) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Agent connecte → l'adapter pilote le voyant, on arrete le health check
|
||||
stopHealthCheck();
|
||||
serverStatus = 'connected';
|
||||
sendServerStatus();
|
||||
|
||||
@@ -466,8 +549,8 @@ ipcMain.handle('logout', async () => {
|
||||
accessCode: currentAgent.accessCode
|
||||
});
|
||||
await adapter.logoff();
|
||||
console.log('Agent deconnecte du serveur');
|
||||
log('Agent deconnecte avec succes', {
|
||||
console.log('Agent déconnecté du serveur');
|
||||
log('Agent déconnecté avec succès', {
|
||||
accessCode: currentAgent.accessCode,
|
||||
name: currentAgent.name
|
||||
});
|
||||
@@ -480,6 +563,9 @@ ipcMain.handle('logout', async () => {
|
||||
currentTerminal = null;
|
||||
agentConnectionInfo = null;
|
||||
|
||||
// Retour ecran login → relancer le health check
|
||||
startHealthCheck();
|
||||
|
||||
if (mainWindow) {
|
||||
mainWindow.setTitle(`SimpleConnect v${app.getVersion()}`);
|
||||
}
|
||||
@@ -488,6 +574,8 @@ ipcMain.handle('logout', async () => {
|
||||
});
|
||||
|
||||
ipcMain.handle('quit-app', async () => {
|
||||
console.log(`${c.dim}${formatTimestamp()}${c.reset} ${c.dim}Application fermée${c.reset}`);
|
||||
logSectionToFile('Application fermée');
|
||||
app.quit();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user