This commit is contained in:
131
services/BaseService.js
Normal file
131
services/BaseService.js
Normal file
@@ -0,0 +1,131 @@
|
||||
const { logger } = require('../config/logs');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
class BaseService {
|
||||
constructor(name, defaultConfig = {}) {
|
||||
this.name = name;
|
||||
this.defaultConfig = defaultConfig;
|
||||
this.config = {};
|
||||
this.isRunning = false;
|
||||
this.status = 'stopped';
|
||||
this.logger = logger;
|
||||
this.setupPath = path.join(process.cwd(), 'data', 'setup.json');
|
||||
}
|
||||
|
||||
async loadConfig() {
|
||||
try {
|
||||
const setupContent = await fs.readFile(this.setupPath, 'utf8');
|
||||
const setup = JSON.parse(setupContent);
|
||||
|
||||
// Retrieve the service configuration from setup.json
|
||||
const serviceConfig = setup[0]?.services?.[this.name] || {};
|
||||
|
||||
// Merge with the default configuration
|
||||
this.config = {
|
||||
...this.defaultConfig,
|
||||
...serviceConfig
|
||||
};
|
||||
|
||||
return this.config;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error loading configuration for ${this.name}:`, error);
|
||||
this.config = this.defaultConfig;
|
||||
return this.config;
|
||||
}
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (this.isRunning) {
|
||||
this.logger.warn(`Service ${this.name} is already running`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the configuration before starting
|
||||
await this.loadConfig();
|
||||
|
||||
// Check if the service is enabled
|
||||
if (this.config.enabled !== 'on') {
|
||||
this.logger.info(`Service ${this.name} is not enabled in the configuration`);
|
||||
return;
|
||||
}
|
||||
|
||||
await this._startImplementation();
|
||||
this.isRunning = true;
|
||||
this.status = 'running';
|
||||
this.logger.info(`Service ${this.name} started successfully`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error starting service ${this.name}:`, error);
|
||||
this.status = 'error';
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (!this.isRunning) {
|
||||
this.logger.warn(`Service ${this.name} is not running`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this._stopImplementation();
|
||||
this.isRunning = false;
|
||||
this.status = 'stopped';
|
||||
this.logger.info(`Service ${this.name} stopped successfully`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error stopping service ${this.name}:`, error);
|
||||
this.status = 'error';
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return {
|
||||
name: this.name,
|
||||
status: this.status,
|
||||
isRunning: this.isRunning,
|
||||
config: this.config
|
||||
};
|
||||
}
|
||||
|
||||
async updateConfig(newConfig) {
|
||||
// Load the current configuration from setup.json
|
||||
let setup;
|
||||
try {
|
||||
const setupContent = await fs.readFile(this.setupPath, 'utf8');
|
||||
setup = JSON.parse(setupContent);
|
||||
} catch (error) {
|
||||
throw new Error(`Error reading setup.json: ${error.message}`);
|
||||
}
|
||||
|
||||
// Update the service configuration
|
||||
if (!setup[0].services) {
|
||||
setup[0].services = {};
|
||||
}
|
||||
setup[0].services[this.name] = {
|
||||
...this.config,
|
||||
...newConfig
|
||||
};
|
||||
|
||||
// Save to setup.json
|
||||
try {
|
||||
await fs.writeFile(this.setupPath, JSON.stringify(setup, null, 2));
|
||||
this.config = setup[0].services[this.name];
|
||||
this.logger.info(`Configuration for service ${this.name} updated:`, this.config);
|
||||
} catch (error) {
|
||||
throw new Error(`Error saving configuration: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// These methods must be implemented by subclasses
|
||||
async _startImplementation() {
|
||||
throw new Error('_startImplementation must be implemented by the subclass');
|
||||
}
|
||||
|
||||
async _stopImplementation() {
|
||||
throw new Error('_stopImplementation must be implemented by the subclass');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseService;
|
||||
141
services/fileCleanupService.js
Normal file
141
services/fileCleanupService.js
Normal file
@@ -0,0 +1,141 @@
|
||||
const BaseService = require('./BaseService');
|
||||
const cron = require('node-cron');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
class FileCleanupService extends BaseService {
|
||||
constructor(config = {}) {
|
||||
super('fileCleanup', {
|
||||
fileInfoPath: config.fileInfoPath || path.join(process.cwd(), 'data', 'file_info.json'),
|
||||
cronSchedule: config.cronSchedule || '0 * * * *',
|
||||
...config
|
||||
});
|
||||
this.job = null;
|
||||
}
|
||||
|
||||
async _startImplementation() {
|
||||
if (this.job) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Valider le cron schedule
|
||||
if (!cron.validate(this.config.cronSchedule)) {
|
||||
throw new Error(`Schedule cron invalide: ${this.config.cronSchedule}`);
|
||||
}
|
||||
|
||||
this.job = cron.schedule(this.config.cronSchedule, () => this.cleanup());
|
||||
}
|
||||
|
||||
async _stopImplementation() {
|
||||
if (this.job) {
|
||||
this.job.stop();
|
||||
this.job = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Réutiliser vos méthodes existantes
|
||||
async fileExists(filepath) {
|
||||
try {
|
||||
await fs.access(filepath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
normalizePath(filepath) {
|
||||
return path.normalize(filepath).replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
async readFileInfo() {
|
||||
try {
|
||||
const data = await fs.readFile(this.config.fileInfoPath, 'utf8');
|
||||
return JSON.parse(data);
|
||||
} catch (err) {
|
||||
this.logger.error('Erreur lors de la lecture du fichier info:', err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async saveFileInfo(fileInfo) {
|
||||
try {
|
||||
await fs.writeFile(
|
||||
this.config.fileInfoPath,
|
||||
JSON.stringify(fileInfo, null, 2),
|
||||
'utf8'
|
||||
);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.logger.error('Erreur lors de la sauvegarde du fichier info:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async processFile(file, now) {
|
||||
const normalizedPath = this.normalizePath(file.path);
|
||||
|
||||
if (file.expiryDate && new Date(file.expiryDate) <= now) {
|
||||
return { status: 'expired', file: { ...file, path: normalizedPath } };
|
||||
}
|
||||
|
||||
if (!(await this.fileExists(normalizedPath))) {
|
||||
return { status: 'missing', file: { ...file, path: normalizedPath } };
|
||||
}
|
||||
|
||||
return { status: 'valid', file };
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
try {
|
||||
const fileInfo = await this.readFileInfo();
|
||||
const now = new Date();
|
||||
const results = {
|
||||
expired: [],
|
||||
missing: [],
|
||||
processed: 0,
|
||||
remaining: 0
|
||||
};
|
||||
|
||||
const validFiles = [];
|
||||
await Promise.all(fileInfo.map(async (file) => {
|
||||
const { status, file: processedFile } = await this.processFile(file, now);
|
||||
|
||||
if (status === 'expired') {
|
||||
results.expired.push(processedFile);
|
||||
results.processed++;
|
||||
} else if (status === 'missing') {
|
||||
results.missing.push(processedFile);
|
||||
results.processed++;
|
||||
} else {
|
||||
validFiles.push(file);
|
||||
}
|
||||
}));
|
||||
|
||||
if (results.processed > 0) {
|
||||
results.remaining = validFiles.length;
|
||||
await this.saveFileInfo(validFiles);
|
||||
|
||||
this.logger.info('Nettoyage des fichiers terminé', {
|
||||
expired: results.expired.length,
|
||||
missing: results.missing.length,
|
||||
totalProcessed: results.processed,
|
||||
remainingFiles: results.remaining
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (err) {
|
||||
this.logger.error('Erreur lors du nettoyage des fichiers:', {
|
||||
error: err.message,
|
||||
stack: err.stack
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async forceCleanup() {
|
||||
return this.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FileCleanupService();
|
||||
181
services/reportService.js
Normal file
181
services/reportService.js
Normal file
@@ -0,0 +1,181 @@
|
||||
const BaseService = require('./BaseService');
|
||||
const cron = require('node-cron');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const ip = require('ip');
|
||||
const si = require('systeminformation');
|
||||
const fetch = require('node-fetch');
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
class ReportManagerService extends BaseService {
|
||||
constructor() {
|
||||
// Configuration par défaut
|
||||
const defaultConfig = {
|
||||
enabled: 'off',
|
||||
endpoint: 'https://cdn-apollon-p198-61m1.dinawo.fr/api/report/receive',
|
||||
cronSchedule: '0 0 * * *' // Par défaut, une fois par jour à minuit
|
||||
};
|
||||
|
||||
super('reportManager', defaultConfig);
|
||||
this.job = null;
|
||||
}
|
||||
|
||||
async _startImplementation() {
|
||||
if (this.job) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Valider le cron schedule
|
||||
const schedule = this.config.cronSchedule || this.defaultConfig.cronSchedule;
|
||||
if (!cron.validate(schedule)) {
|
||||
throw new Error(`Schedule cron invalide: ${schedule}`);
|
||||
}
|
||||
|
||||
this.job = cron.schedule(schedule, () => this.generateAndSendReport());
|
||||
this.logger.info(`Service de rapport programmé avec le planning: ${schedule}`);
|
||||
}
|
||||
|
||||
async _stopImplementation() {
|
||||
if (this.job) {
|
||||
this.job.stop();
|
||||
this.job = null;
|
||||
}
|
||||
}
|
||||
|
||||
formatUptime(uptime) {
|
||||
const days = Math.floor(uptime / (24 * 60 * 60));
|
||||
uptime %= (24 * 60 * 60);
|
||||
const hours = Math.floor(uptime / (60 * 60));
|
||||
uptime %= (60 * 60);
|
||||
const minutes = Math.floor(uptime / 60);
|
||||
return `${days}d ${hours}h ${minutes}m`;
|
||||
}
|
||||
|
||||
async getInternalErrors() {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 1);
|
||||
const previousDate = date.toISOString().split('T')[0];
|
||||
const logFile = path.join(__dirname, '..', 'logs', `log-${previousDate}.log`);
|
||||
|
||||
try {
|
||||
const logs = fs.readFileSync(logFile, 'utf-8');
|
||||
return logs.split('\n').filter(line => /\[38;5;9mInternal-Error/.test(line));
|
||||
} catch (err) {
|
||||
this.logger.error('Erreur lors de la lecture des logs:', err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async generateReport() {
|
||||
try {
|
||||
const internalErrors = await this.getInternalErrors();
|
||||
|
||||
if (internalErrors.length === 0) {
|
||||
this.logger.info('Pas d\'erreurs internes dans les logs d\'hier. Pas de rapport généré.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const loadavg = os.loadavg().map(load => (load / os.cpus().length) * 100);
|
||||
|
||||
const osInfo = {
|
||||
type: os.type(),
|
||||
platform: os.platform(),
|
||||
arch: os.arch(),
|
||||
release: os.release(),
|
||||
uptime: this.formatUptime(os.uptime()),
|
||||
loadavg: loadavg
|
||||
};
|
||||
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
const diskUsage = await si.fsSize();
|
||||
const cpuTemperature = await si.cpuTemperature();
|
||||
const userInfo = os.userInfo();
|
||||
|
||||
const systemInfo = {
|
||||
memoryInfo: ((os.totalmem() - os.freemem()) / os.totalmem() * 100).toFixed(2),
|
||||
cpuInfo: (os.cpus().length / os.cpus().length * 100).toFixed(2),
|
||||
diskInfo: diskUsage.map(disk => ({
|
||||
fs: disk.fs,
|
||||
type: disk.type,
|
||||
size: disk.size,
|
||||
used: disk.used,
|
||||
available: disk.available,
|
||||
use: disk.use
|
||||
})),
|
||||
ipAddress: ip.address(),
|
||||
cdnVersion: packageJson.version,
|
||||
osInfo: osInfo,
|
||||
userInfo: {
|
||||
username: userInfo.username,
|
||||
homedir: userInfo.homedir,
|
||||
shell: userInfo.shell
|
||||
},
|
||||
errors: internalErrors,
|
||||
networkInterfaces: networkInterfaces,
|
||||
serverUptime: os.uptime(),
|
||||
systemLoad: loadavg,
|
||||
cpuTemperature: cpuTemperature
|
||||
};
|
||||
|
||||
const reportDate = new Date();
|
||||
reportDate.setDate(reportDate.getDate() - 1);
|
||||
const reportFileName = `report_${reportDate.toISOString().split('T')[0]}_${ip.address()}.json`;
|
||||
|
||||
// Sauvegarder le rapport localement
|
||||
const reportDir = path.join(__dirname, '..', 'report');
|
||||
if (!fs.existsSync(reportDir)) {
|
||||
fs.mkdirSync(reportDir, { recursive: true });
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(reportDir, reportFileName),
|
||||
JSON.stringify(systemInfo, null, 2)
|
||||
);
|
||||
|
||||
return systemInfo;
|
||||
} catch (error) {
|
||||
this.logger.error('Erreur lors de la génération du rapport:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async generateAndSendReport() {
|
||||
try {
|
||||
const report = await this.generateReport();
|
||||
|
||||
if (!report) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info("Envoi du rapport...");
|
||||
|
||||
const response = await fetch(this.config.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(report)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP! statut: ${response.status}`);
|
||||
}
|
||||
|
||||
const responseData = await response.json();
|
||||
this.logger.info('Rapport envoyé avec succès. Réponse:', responseData);
|
||||
|
||||
return responseData;
|
||||
} catch (error) {
|
||||
this.logger.error('Échec de l\'envoi du rapport:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode pour forcer l'envoi d'un rapport immédiatement
|
||||
async forceSendReport() {
|
||||
return this.generateAndSendReport();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ReportManagerService();
|
||||
Reference in New Issue
Block a user