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) {
|
function logToFile(message, data = null) {
|
||||||
ensureLogDirectory();
|
ensureLogDirectory();
|
||||||
|
let line = `[${fileTimestamp()}] ${stripAnsi(message)}`;
|
||||||
|
if (data) line += ' ' + JSON.stringify(data);
|
||||||
|
fs.appendFileSync(LOG_FILE, line + '\n', 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
const timestamp = new Date().toISOString();
|
function logSectionToFile(message) {
|
||||||
let logEntry = `[${timestamp}] ${message}`;
|
ensureLogDirectory();
|
||||||
|
fs.appendFileSync(LOG_FILE, `[${fileTimestamp()}] ── ${stripAnsi(message)} ──\n`, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
if (data) {
|
// ANSI colors
|
||||||
logEntry += '\n' + JSON.stringify(data, null, 2);
|
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';
|
function formatTimestamp() {
|
||||||
|
return new Date().toLocaleString('fr-FR', {
|
||||||
fs.appendFileSync(LOG_FILE, logEntry, 'utf8');
|
timeZone: 'Europe/Paris',
|
||||||
|
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(message, data = null) {
|
function log(message, data = null) {
|
||||||
console.log(message, data || '');
|
console.log(`${c.dim}${formatTimestamp()}${c.reset} ${message}`);
|
||||||
logToFile(message, data);
|
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
|
// Charger la configuration
|
||||||
function loadConfig() {
|
function loadConfig() {
|
||||||
const configPath = path.join(__dirname, 'config.json');
|
const configPath = path.join(__dirname, 'config.json');
|
||||||
@@ -92,49 +126,77 @@ function initializeSocketIO() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logBanner(app.getVersion(), config.socketio.serverUrl);
|
||||||
|
|
||||||
adapter = new SocketIOAdapter(config.socketio.serverUrl);
|
adapter = new SocketIOAdapter(config.socketio.serverUrl);
|
||||||
|
|
||||||
// Demarrer le health check polling
|
// Demarrer le health check (ecran de login)
|
||||||
startHealthCheck();
|
startHealthCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health check periodique via GET /health
|
// Health check periodique via GET /health
|
||||||
|
// Actif uniquement sur l'ecran de login (pas d'agent connecte)
|
||||||
function startHealthCheck() {
|
function startHealthCheck() {
|
||||||
const checkHealth = () => {
|
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;
|
const serverUrl = config.socketio.serverUrl;
|
||||||
|
let done = false;
|
||||||
|
|
||||||
const request = net.request(`${serverUrl}/health`);
|
const request = net.request(`${serverUrl}/health`);
|
||||||
|
|
||||||
request.on('response', (response) => {
|
request.on('response', (response) => {
|
||||||
if (response.statusCode === 200) {
|
if (done) return;
|
||||||
if (serverStatus !== 'connected') {
|
done = true;
|
||||||
// Ne passer en 'connected' que si on n'a pas d'agent connecte
|
const newStatus = response.statusCode === 200 ? 'connected' : 'error';
|
||||||
// (sinon le status est deja 'connected' via le login)
|
if (serverStatus !== newStatus) {
|
||||||
if (!currentAgent) {
|
serverStatus = newStatus;
|
||||||
serverStatus = 'connected';
|
|
||||||
sendServerStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
serverStatus = 'error';
|
|
||||||
sendServerStatus();
|
sendServerStatus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
request.on('error', () => {
|
request.on('error', () => {
|
||||||
serverStatus = 'error';
|
if (done) return;
|
||||||
sendServerStatus();
|
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();
|
request.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check immediat puis toutes les 10s
|
// Check immediat puis toutes les 5s
|
||||||
checkHealth();
|
checkHealth();
|
||||||
healthCheckInterval = setInterval(checkHealth, 10000);
|
healthCheckInterval = setInterval(checkHealth, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopHealthCheck() {
|
||||||
|
if (healthCheckInterval) {
|
||||||
|
clearInterval(healthCheckInterval);
|
||||||
|
healthCheckInterval = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendServerStatus() {
|
function sendServerStatus() {
|
||||||
|
if (serverStatus !== 'connected') {
|
||||||
|
log(`${c.red}✗${c.reset} Serveur injoignable`);
|
||||||
|
}
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.webContents.send('server-status', serverStatus);
|
mainWindow.webContents.send('server-status', serverStatus);
|
||||||
}
|
}
|
||||||
@@ -177,7 +239,7 @@ function handleCallHungUp(event) {
|
|||||||
function setupEventHandlers() {
|
function setupEventHandlers() {
|
||||||
if (!adapter) return;
|
if (!adapter) return;
|
||||||
|
|
||||||
log('Session Socket.IO demarree', {
|
log('Session Socket.IO démarrée', {
|
||||||
serverUrl: config.socketio.serverUrl,
|
serverUrl: config.socketio.serverUrl,
|
||||||
serviceProvider: config.socketio.serviceProvider
|
serviceProvider: config.socketio.serviceProvider
|
||||||
});
|
});
|
||||||
@@ -237,17 +299,18 @@ app.on('window-all-closed', async () => {
|
|||||||
clearInterval(healthCheckInterval);
|
clearInterval(healthCheckInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deconnexion propre avant de quitter
|
// Déconnexion propre avant de quitter
|
||||||
if (currentAgent && adapter) {
|
if (currentAgent && adapter) {
|
||||||
try {
|
try {
|
||||||
await adapter.logoff();
|
await adapter.logoff();
|
||||||
console.log('Agent deconnecte avant fermeture');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur deconnexion:', error);
|
console.error('Erreur déconnexion:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
|
console.log(`${c.dim}${formatTimestamp()}${c.reset} ${c.dim}Application fermée${c.reset}`);
|
||||||
|
logSectionToFile('Application fermée');
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -282,8 +345,6 @@ ipcMain.handle('get-terminal-list', async () => {
|
|||||||
try {
|
try {
|
||||||
const provider = config.socketio.serviceProvider;
|
const provider = config.socketio.serviceProvider;
|
||||||
const url = `${config.socketio.serverUrl}/terminals?provider=${encodeURIComponent(provider)}`;
|
const url = `${config.socketio.serverUrl}/terminals?provider=${encodeURIComponent(provider)}`;
|
||||||
console.log('Recuperation des terminaux:', url);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const request = net.request(url);
|
const request = net.request(url);
|
||||||
let body = '';
|
let body = '';
|
||||||
@@ -297,7 +358,7 @@ ipcMain.handle('get-terminal-list', async () => {
|
|||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
try {
|
try {
|
||||||
const terminals = JSON.parse(body);
|
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 : []);
|
resolve(Array.isArray(terminals) ? terminals : []);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Erreur parsing terminaux:', 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
|
// Recreer l'adapter pour une connexion fraiche
|
||||||
adapter = new SocketIOAdapter(config.socketio.serverUrl);
|
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
|
// Configurer les handlers d'evenements AVANT connect
|
||||||
setupEventHandlers();
|
setupEventHandlers();
|
||||||
|
|
||||||
@@ -359,8 +440,8 @@ ipcMain.handle('login-agent', async (event, credentials) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
console.log('Connexion reussie:', result);
|
console.log('Connexion réussie:', result);
|
||||||
log('Connexion agent reussie', {
|
log('Connexion agent réussie', {
|
||||||
accessCode: result.accessCode,
|
accessCode: result.accessCode,
|
||||||
firstName: result.firstName,
|
firstName: result.firstName,
|
||||||
lastName: result.lastName,
|
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';
|
serverStatus = 'connected';
|
||||||
sendServerStatus();
|
sendServerStatus();
|
||||||
|
|
||||||
@@ -466,8 +549,8 @@ ipcMain.handle('logout', async () => {
|
|||||||
accessCode: currentAgent.accessCode
|
accessCode: currentAgent.accessCode
|
||||||
});
|
});
|
||||||
await adapter.logoff();
|
await adapter.logoff();
|
||||||
console.log('Agent deconnecte du serveur');
|
console.log('Agent déconnecté du serveur');
|
||||||
log('Agent deconnecte avec succes', {
|
log('Agent déconnecté avec succès', {
|
||||||
accessCode: currentAgent.accessCode,
|
accessCode: currentAgent.accessCode,
|
||||||
name: currentAgent.name
|
name: currentAgent.name
|
||||||
});
|
});
|
||||||
@@ -480,6 +563,9 @@ ipcMain.handle('logout', async () => {
|
|||||||
currentTerminal = null;
|
currentTerminal = null;
|
||||||
agentConnectionInfo = null;
|
agentConnectionInfo = null;
|
||||||
|
|
||||||
|
// Retour ecran login → relancer le health check
|
||||||
|
startHealthCheck();
|
||||||
|
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.setTitle(`SimpleConnect v${app.getVersion()}`);
|
mainWindow.setTitle(`SimpleConnect v${app.getVersion()}`);
|
||||||
}
|
}
|
||||||
@@ -488,6 +574,8 @@ ipcMain.handle('logout', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('quit-app', 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();
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user