485 lines
13 KiB
JavaScript
485 lines
13 KiB
JavaScript
// ========================================
|
|
// FICHIER: ModeManager.js
|
|
// RESPONSABILITÉ: Gestionnaire modes exclusifs serveur
|
|
// MODES: MANUAL (interface client) | AUTO (traitement batch GSheets)
|
|
// ========================================
|
|
|
|
const { logSh } = require('../ErrorReporting');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
/**
|
|
* GESTIONNAIRE MODES EXCLUSIFS
|
|
* Gère le basculement entre mode MANUAL et AUTO de façon exclusive
|
|
*/
|
|
class ModeManager {
|
|
|
|
// ========================================
|
|
// CONSTANTES ET ÉTAT
|
|
// ========================================
|
|
static MODES = {
|
|
MANUAL: 'manual', // Interface client + API + WebSocket
|
|
AUTO: 'auto' // Traitement batch Google Sheets
|
|
};
|
|
|
|
static currentMode = null;
|
|
static isLocked = false;
|
|
static lockReason = null;
|
|
static modeStartTime = null;
|
|
static activeServices = {
|
|
manualServer: null,
|
|
autoProcessor: null,
|
|
websocketServer: null
|
|
};
|
|
|
|
// Stats par mode
|
|
static stats = {
|
|
manual: { sessions: 0, requests: 0, lastActivity: null },
|
|
auto: { processed: 0, errors: 0, lastProcessing: null }
|
|
};
|
|
|
|
// ========================================
|
|
// INITIALISATION ET DÉTECTION MODE
|
|
// ========================================
|
|
|
|
/**
|
|
* Initialise le gestionnaire de modes
|
|
* @param {string} initialMode - Mode initial (manual|auto|detect)
|
|
*/
|
|
static async initialize(initialMode = 'detect') {
|
|
logSh('🎛️ Initialisation ModeManager...', 'INFO');
|
|
|
|
try {
|
|
// Détecter mode selon arguments ou config
|
|
const detectedMode = this.detectIntendedMode(initialMode);
|
|
|
|
logSh(`🎯 Mode détecté: ${detectedMode.toUpperCase()}`, 'INFO');
|
|
|
|
// Nettoyer état précédent si nécessaire
|
|
await this.cleanupPreviousState();
|
|
|
|
// Basculer vers le mode détecté
|
|
await this.switchToMode(detectedMode);
|
|
|
|
// Sauvegarder état
|
|
this.saveModeState();
|
|
|
|
logSh(`✅ ModeManager initialisé en mode ${this.currentMode.toUpperCase()}`, 'INFO');
|
|
|
|
return this.currentMode;
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur initialisation ModeManager: ${error.message}`, 'ERROR');
|
|
throw new Error(`Échec initialisation ModeManager: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Détecte le mode souhaité selon arguments CLI et env
|
|
*/
|
|
static detectIntendedMode(initialMode) {
|
|
// 1. Argument explicite
|
|
if (initialMode === this.MODES.MANUAL || initialMode === this.MODES.AUTO) {
|
|
return initialMode;
|
|
}
|
|
|
|
// 2. Arguments de ligne de commande
|
|
const args = process.argv.slice(2);
|
|
const modeArg = args.find(arg => arg.startsWith('--mode='));
|
|
if (modeArg) {
|
|
const mode = modeArg.split('=')[1];
|
|
if (Object.values(this.MODES).includes(mode)) {
|
|
return mode;
|
|
}
|
|
}
|
|
|
|
// 3. Variable d'environnement
|
|
const envMode = process.env.SERVER_MODE?.toLowerCase();
|
|
if (Object.values(this.MODES).includes(envMode)) {
|
|
return envMode;
|
|
}
|
|
|
|
// 4. Script npm spécifique
|
|
const npmScript = process.env.npm_lifecycle_event;
|
|
if (npmScript === 'auto') return this.MODES.AUTO;
|
|
|
|
// 5. Défaut = MANUAL
|
|
return this.MODES.MANUAL;
|
|
}
|
|
|
|
// ========================================
|
|
// CHANGEMENT DE MODES
|
|
// ========================================
|
|
|
|
/**
|
|
* Bascule vers un mode spécifique
|
|
*/
|
|
static async switchToMode(targetMode, force = false) {
|
|
if (!Object.values(this.MODES).includes(targetMode)) {
|
|
throw new Error(`Mode invalide: ${targetMode}`);
|
|
}
|
|
|
|
if (this.currentMode === targetMode) {
|
|
logSh(`Mode ${targetMode} déjà actif`, 'DEBUG');
|
|
return true;
|
|
}
|
|
|
|
// Vérifier si changement possible
|
|
if (!force && !await this.canSwitchToMode(targetMode)) {
|
|
throw new Error(`Impossible de basculer vers ${targetMode}: ${this.lockReason}`);
|
|
}
|
|
|
|
logSh(`🔄 Basculement ${this.currentMode || 'NONE'} → ${targetMode}...`, 'INFO');
|
|
|
|
try {
|
|
// Arrêter mode actuel
|
|
await this.stopCurrentMode();
|
|
|
|
// Démarrer nouveau mode
|
|
await this.startMode(targetMode);
|
|
|
|
// Mettre à jour état
|
|
this.currentMode = targetMode;
|
|
this.modeStartTime = Date.now();
|
|
this.lockReason = null;
|
|
|
|
logSh(`✅ Basculement terminé: Mode ${targetMode.toUpperCase()} actif`, 'INFO');
|
|
|
|
return true;
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Échec basculement vers ${targetMode}: ${error.message}`, 'ERROR');
|
|
|
|
// Tentative de récupération
|
|
try {
|
|
await this.emergencyRecovery();
|
|
} catch (recoveryError) {
|
|
logSh(`❌ Échec récupération d'urgence: ${recoveryError.message}`, 'ERROR');
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Vérifie si le basculement est possible
|
|
*/
|
|
static async canSwitchToMode(targetMode) {
|
|
// Mode verrouillé
|
|
if (this.isLocked) {
|
|
this.lockReason = 'Mode verrouillé pour opération critique';
|
|
return false;
|
|
}
|
|
|
|
// Vérifications spécifiques par mode
|
|
switch (targetMode) {
|
|
case this.MODES.MANUAL:
|
|
return await this.canSwitchToManual();
|
|
|
|
case this.MODES.AUTO:
|
|
return await this.canSwitchToAuto();
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Peut-on basculer vers MANUAL ?
|
|
*/
|
|
static async canSwitchToManual() {
|
|
// Si mode AUTO actif, vérifier processus
|
|
if (this.currentMode === this.MODES.AUTO) {
|
|
const autoProcessor = this.activeServices.autoProcessor;
|
|
|
|
if (autoProcessor && autoProcessor.isProcessing()) {
|
|
this.lockReason = 'Traitement automatique en cours, arrêt requis';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Peut-on basculer vers AUTO ?
|
|
*/
|
|
static async canSwitchToAuto() {
|
|
// Si mode MANUAL actif, vérifier clients
|
|
if (this.currentMode === this.MODES.MANUAL) {
|
|
const manualServer = this.activeServices.manualServer;
|
|
|
|
if (manualServer && manualServer.hasActiveClients()) {
|
|
this.lockReason = 'Clients actifs en mode MANUAL, déconnexion requise';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ========================================
|
|
// DÉMARRAGE ET ARRÊT SERVICES
|
|
// ========================================
|
|
|
|
/**
|
|
* Démarre un mode spécifique
|
|
*/
|
|
static async startMode(mode) {
|
|
logSh(`🚀 Démarrage mode ${mode.toUpperCase()}...`, 'DEBUG');
|
|
|
|
switch (mode) {
|
|
case this.MODES.MANUAL:
|
|
await this.startManualMode();
|
|
break;
|
|
|
|
case this.MODES.AUTO:
|
|
await this.startAutoMode();
|
|
break;
|
|
|
|
default:
|
|
throw new Error(`Mode de démarrage inconnu: ${mode}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Démarre le mode MANUAL
|
|
*/
|
|
static async startManualMode() {
|
|
const { ManualServer } = require('./ManualServer');
|
|
|
|
logSh('🎯 Démarrage ManualServer...', 'DEBUG');
|
|
|
|
this.activeServices.manualServer = new ManualServer();
|
|
await this.activeServices.manualServer.start();
|
|
|
|
logSh('✅ Mode MANUAL démarré', 'DEBUG');
|
|
}
|
|
|
|
/**
|
|
* Démarre le mode AUTO
|
|
*/
|
|
static async startAutoMode() {
|
|
const { AutoProcessor } = require('./AutoProcessor');
|
|
|
|
logSh('🤖 Démarrage AutoProcessor...', 'DEBUG');
|
|
|
|
this.activeServices.autoProcessor = new AutoProcessor();
|
|
await this.activeServices.autoProcessor.start();
|
|
|
|
logSh('✅ Mode AUTO démarré', 'DEBUG');
|
|
}
|
|
|
|
/**
|
|
* Arrête le mode actuel
|
|
*/
|
|
static async stopCurrentMode() {
|
|
if (!this.currentMode) return;
|
|
|
|
logSh(`🛑 Arrêt mode ${this.currentMode.toUpperCase()}...`, 'DEBUG');
|
|
|
|
try {
|
|
switch (this.currentMode) {
|
|
case this.MODES.MANUAL:
|
|
await this.stopManualMode();
|
|
break;
|
|
|
|
case this.MODES.AUTO:
|
|
await this.stopAutoMode();
|
|
break;
|
|
}
|
|
|
|
logSh(`✅ Mode ${this.currentMode.toUpperCase()} arrêté`, 'DEBUG');
|
|
|
|
} catch (error) {
|
|
logSh(`⚠️ Erreur arrêt mode ${this.currentMode}: ${error.message}`, 'WARNING');
|
|
// Continue malgré l'erreur pour permettre le changement
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Arrête le mode MANUAL
|
|
*/
|
|
static async stopManualMode() {
|
|
if (this.activeServices.manualServer) {
|
|
await this.activeServices.manualServer.stop();
|
|
this.activeServices.manualServer = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Arrête le mode AUTO
|
|
*/
|
|
static async stopAutoMode() {
|
|
if (this.activeServices.autoProcessor) {
|
|
await this.activeServices.autoProcessor.stop();
|
|
this.activeServices.autoProcessor = null;
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// ÉTAT ET MONITORING
|
|
// ========================================
|
|
|
|
/**
|
|
* État actuel du gestionnaire
|
|
*/
|
|
static getStatus() {
|
|
return {
|
|
currentMode: this.currentMode,
|
|
isLocked: this.isLocked,
|
|
lockReason: this.lockReason,
|
|
modeStartTime: this.modeStartTime,
|
|
uptime: this.modeStartTime ? Date.now() - this.modeStartTime : 0,
|
|
stats: { ...this.stats },
|
|
activeServices: {
|
|
manualServer: !!this.activeServices.manualServer,
|
|
autoProcessor: !!this.activeServices.autoProcessor
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Vérifie si mode MANUAL actif
|
|
*/
|
|
static isManualMode() {
|
|
return this.currentMode === this.MODES.MANUAL;
|
|
}
|
|
|
|
/**
|
|
* Vérifie si mode AUTO actif
|
|
*/
|
|
static isAutoMode() {
|
|
return this.currentMode === this.MODES.AUTO;
|
|
}
|
|
|
|
/**
|
|
* Verrouille le mode actuel
|
|
*/
|
|
static lockMode(reason = 'Opération critique') {
|
|
this.isLocked = true;
|
|
this.lockReason = reason;
|
|
logSh(`🔒 Mode ${this.currentMode} verrouillé: ${reason}`, 'INFO');
|
|
}
|
|
|
|
/**
|
|
* Déverrouille le mode
|
|
*/
|
|
static unlockMode() {
|
|
this.isLocked = false;
|
|
this.lockReason = null;
|
|
logSh(`🔓 Mode ${this.currentMode} déverrouillé`, 'INFO');
|
|
}
|
|
|
|
// ========================================
|
|
// GESTION ERREURS ET RÉCUPÉRATION
|
|
// ========================================
|
|
|
|
/**
|
|
* Nettoyage état précédent
|
|
*/
|
|
static async cleanupPreviousState() {
|
|
logSh('🧹 Nettoyage état précédent...', 'DEBUG');
|
|
|
|
// Arrêter tous les services actifs
|
|
await this.stopCurrentMode();
|
|
|
|
// Reset état
|
|
this.isLocked = false;
|
|
this.lockReason = null;
|
|
|
|
logSh('✅ Nettoyage terminé', 'DEBUG');
|
|
}
|
|
|
|
/**
|
|
* Récupération d'urgence
|
|
*/
|
|
static async emergencyRecovery() {
|
|
logSh('🚨 Récupération d\'urgence...', 'WARNING');
|
|
|
|
try {
|
|
// Forcer arrêt de tous les services
|
|
await this.forceStopAllServices();
|
|
|
|
// Reset état complet
|
|
this.currentMode = null;
|
|
this.isLocked = false;
|
|
this.lockReason = null;
|
|
this.modeStartTime = null;
|
|
|
|
logSh('✅ Récupération d\'urgence terminée', 'INFO');
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Échec récupération d'urgence: ${error.message}`, 'ERROR');
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Arrêt forcé de tous les services
|
|
*/
|
|
static async forceStopAllServices() {
|
|
const services = Object.keys(this.activeServices);
|
|
|
|
for (const serviceKey of services) {
|
|
const service = this.activeServices[serviceKey];
|
|
if (service) {
|
|
try {
|
|
if (typeof service.stop === 'function') {
|
|
await service.stop();
|
|
}
|
|
} catch (error) {
|
|
logSh(`⚠️ Erreur arrêt forcé ${serviceKey}: ${error.message}`, 'WARNING');
|
|
}
|
|
this.activeServices[serviceKey] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// PERSISTANCE ET CONFIGURATION
|
|
// ========================================
|
|
|
|
/**
|
|
* Sauvegarde l'état du mode
|
|
*/
|
|
static saveModeState() {
|
|
try {
|
|
const stateFile = path.join(__dirname, '../..', 'mode-state.json');
|
|
const state = {
|
|
currentMode: this.currentMode,
|
|
modeStartTime: this.modeStartTime,
|
|
stats: this.stats,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
|
|
|
|
} catch (error) {
|
|
logSh(`⚠️ Erreur sauvegarde état mode: ${error.message}`, 'WARNING');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restaure l'état du mode
|
|
*/
|
|
static loadModeState() {
|
|
try {
|
|
const stateFile = path.join(__dirname, '../..', 'mode-state.json');
|
|
|
|
if (fs.existsSync(stateFile)) {
|
|
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
this.stats = state.stats || this.stats;
|
|
return state;
|
|
}
|
|
|
|
} catch (error) {
|
|
logSh(`⚠️ Erreur chargement état mode: ${error.message}`, 'WARNING');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ============= EXPORTS =============
|
|
module.exports = { ModeManager }; |