diff --git a/.drone.yml b/.drone.yml index c2130ee..66ffcde 100644 --- a/.drone.yml +++ b/.drone.yml @@ -10,14 +10,14 @@ steps: from_secret: git_password commands: - npm install - - export VERSION=$(node -e "console.log(require('./package.json').version)") + - echo "VERSION=$(node -e "console.log(require('./package.json').version)")" >> .env + - cat .env >> .tags + - name: build-docker-image image: plugins/docker settings: repo: swiftlogiclabs/cdn-app-insider - tags: - - latest - - v${VERSION} + tags_file: .tags dockerfile: Dockerfile username: from_secret: docker_username diff --git a/config/logs.js b/config/logs.js index ce141d4..60de6d6 100644 --- a/config/logs.js +++ b/config/logs.js @@ -1,147 +1,194 @@ const { format } = require('winston'); const winston = require('winston'); const DailyRotateFile = require('winston-daily-rotate-file'); +const fs = require('fs'); +const path = require('path'); +// Configuration des préfixes de logs avec couleurs const logPrefix = (label) => { - if (label === 'server') { - return '[🖥️ \x1b[32mServer\x1b[0m] '; - } else if (label === 'client') { - return '[🌐 \x1b[34mClient\x1b[0m] '; - } else if (label === 'Internal-Server-Error') { - return '[❗️ \x1b[38;5;9mInternal-Error\x1b[0m] '; - } else if (label === 'Authentification') { - return '[🔒 \x1b[33mAuthentification\x1b[0m] '; - } else if (label === 'Suspicious Request') { - return '[⚠️ \x1b[38;5;208mSuspicious Request\x1b[0m] '; - }else if (label === 'API Request') { - return '[⚙️ \x1b[38;5;9mAPI Request\x1b[0m] '; - } - return ''; + const prefixes = { + 'server': '[🖥️ \x1b[32mServer\x1b[0m]', + 'client': '[🌐 \x1b[34mClient\x1b[0m]', + 'Internal-Server-Error': '[❗️ \x1b[38;5;9mInternal-Error\x1b[0m]', + 'Authentification': '[🔒 \x1b[33mAuthentification\x1b[0m]', + 'Suspicious Request': '[⚠️ \x1b[38;5;208mSuspicious Request\x1b[0m]', + 'API Request': '[⚙️ \x1b[38;5;9mAPI Request\x1b[0m]', + 'File System': '[📁 \x1b[36mFile System\x1b[0m]', + 'Database': '[🗄️ \x1b[35mDatabase\x1b[0m]' + }; + return prefixes[label] || ''; }; +// Configuration du transport de fichiers rotatifs const createDailyRotateFileTransport = () => { - return new DailyRotateFile({ - filename: 'logs/log-%DATE%.log', - datePattern: 'YYYY-MM-DD', - zippedArchive: false, - maxSize: '20m', - maxFiles: '14d' - }); + return new DailyRotateFile({ + filename: 'logs/log-%DATE%.log', + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '14d', + format: format.combine( + format.timestamp(), + format.json() + ) + }); }; -const logger = winston.createLogger({ - format: format.combine( - format.label({ label: 'server' }), - format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - format.printf(info => { - const { timestamp, level, label, message } = info; - const prefix = logPrefix(label); - return `${prefix}[\x1b[36m${timestamp}\x1b[0m] ${message}`; - }) - ), - transports: [ - new winston.transports.Console(), - createDailyRotateFileTransport() - ] -}); +// Configuration du format de base pour tous les loggers +const createLoggerFormat = (label) => { + return format.combine( + format.label({ label }), + format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + format.printf(info => { + const { timestamp, label, message } = info; + const prefix = logPrefix(label); + return `${prefix} [\x1b[36m${timestamp}\x1b[0m] ${message}`; + }) + ); +}; -const ErrorLogger = winston.createLogger({ - format: format.combine( - format.label({ label: 'Internal-Server-Error' }), - format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - format.printf(info => { - const { timestamp, level, label, message } = info; - const prefix = logPrefix(label); - return `${prefix}[\x1b[36m${timestamp}\x1b[0m] ${message}`; - }) - ), - transports: [ - new winston.transports.Console(), - createDailyRotateFileTransport() - ] -}); +// Création des différents loggers +const createLogger = (label) => { + return winston.createLogger({ + format: createLoggerFormat(label), + transports: [ + new winston.transports.Console(), + createDailyRotateFileTransport() + ] + }); +}; -const clientLogger = winston.createLogger({ - format: format.combine( - format.label({ label: 'client' }), - format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - format.printf(info => { - const { timestamp, level, label, message } = info; - const prefix = logPrefix(label); - return `${prefix}[\x1b[36m${timestamp}\x1b[0m] ${message}`; - }) - ), - transports: [ - new winston.transports.Console(), - createDailyRotateFileTransport() - ] -}); +// Instanciation des loggers +const logger = createLogger('server'); +const clientLogger = createLogger('client'); +const ErrorLogger = createLogger('Internal-Server-Error'); +const authLogger = createLogger('Authentification'); +const suspiciousLogger = createLogger('Suspicious Request'); +const apiLogger = createLogger('API Request'); +const fileSystemLogger = createLogger('File System'); +const databaseLogger = createLogger('Database'); -const authLogger = winston.createLogger({ - format: format.combine( - format.label({ label: 'Authentification' }), - format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - format.printf(info => { - const { timestamp, level, label, message } = info; - const prefix = logPrefix(label); - return `${prefix}[\x1b[36m${timestamp}\x1b[0m] ${message}`; - }) - ), - transports: [ - new winston.transports.Console(), - createDailyRotateFileTransport() - ] -}); - -const suspiciousLogger = winston.createLogger({ - format: format.combine( - format.label({ label: 'Suspicious Request' }), - format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - format.printf(info => { - const { timestamp, level, label, message } = info; - const prefix = logPrefix(label); - return `${prefix}[\x1b[36m${timestamp}\x1b[0m] ${message}`; - }) - ), - transports: [ - new winston.transports.Console(), - createDailyRotateFileTransport() - ] -}); +// Fonction utilitaire pour vérifier les chemins +const checkPath = (url, paths) => { + return paths && paths.length > 0 && paths.some(p => url.startsWith(p.trim())); +}; +// Middleware principal de logging const logRequestInfo = (req, res, next) => { - const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; - const userAgent = req.headers['user-agent']; - req.log = clientLogger; - req.log.info(`[${ip}] - ${userAgent} - ${req.method} ${req.url}`); - next(); -}; + try { + // Lire la configuration + const setup = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'data', 'setup.json'), 'utf-8'))[0]; + const logsConfig = setup.logs || { enabled: 'off', level: 'info' }; -const apiLogger = winston.createLogger({ - format: format.combine( - format.label({ label: 'API Request' }), - format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - format.printf(info => { - const { timestamp, level, label, message } = info; - const prefix = logPrefix(label); - return `${prefix}[\x1b[36m${timestamp}\x1b[0m] ${message}`; - }) - ), - transports: [ - new winston.transports.Console(), - createDailyRotateFileTransport() - ] -}); + // Vérifier si les logs sont activés + if (logsConfig.enabled !== 'on') { + return next(); + } + const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; + const userAgent = req.headers['user-agent']; + const url = req.originalUrl || req.url; + + // Vérifier d'abord includeOnly + if (logsConfig.includeOnly && logsConfig.includeOnly.length > 0) { + const isIncluded = logsConfig.includeOnly.some(p => url.startsWith(p.trim())); + if (!isIncluded) { + return next(); // Ne pas logger si le chemin n'est pas dans includeOnly + } + } + + // Ensuite vérifier excludePaths + if (logsConfig.excludePaths && logsConfig.excludePaths.length > 0) { + const isExcluded = logsConfig.excludePaths.some(p => url.startsWith(p.trim())); + if (isExcluded) { + return next(); // Ne pas logger si le chemin est dans excludePaths + } + } + + // Sélectionner le logger approprié et le niveau + let selectedLogger = clientLogger; + let logLevel = logsConfig.level || 'info'; + + if (url.startsWith('/api')) { + selectedLogger = apiLogger; + } else if (url.startsWith('/auth')) { + selectedLogger = authLogger; + } + + // Ne logger que si le niveau est activé + if (!logsConfig.levels || logsConfig.levels.includes(logLevel)) { + const logMessage = `[${ip}] - ${userAgent} - ${req.method} ${url}`; + selectedLogger[logLevel](logMessage); + } + + } catch (err) { + console.error('Error in logRequestInfo:', err); + } + + next(); +} + +// Middleware spécifique pour les requêtes API const logApiRequest = (req, res, next) => { - const start = Date.now(); - res.on('finish', () => { - const duration = Date.now() - start; - const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; - apiLogger.info(`[${ip}] - ${req.method} ${req.originalUrl} - ${res.statusCode} - ${duration}ms`); - }); - next(); + if (!req.originalUrl.startsWith('/api')) { + return next(); + } + + const start = Date.now(); + res.on('finish', () => { + try { + const setup = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'data', 'setup.json'), 'utf-8'))[0]; + const logsConfig = setup.logs || { enabled: 'off' }; + + if (logsConfig.enabled !== 'on' || + (logsConfig.levels && !logsConfig.levels.includes('info'))) { + return; + } + + const duration = Date.now() - start; + const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; + apiLogger.info(`[${ip}] - ${req.method} ${req.originalUrl} - ${res.statusCode} - ${duration}ms`); + } catch (err) { + ErrorLogger.error(`Error in API logging: ${err.message}`); + } + }); + next(); }; +// Fonction utilitaire pour logger les erreurs système +const logSystemError = (error, context = '') => { + const errorMessage = `${context ? `[${context}] ` : ''}${error.message}`; + ErrorLogger.error(errorMessage); + if (error.stack) { + ErrorLogger.error(error.stack); + } +}; -module.exports = { logger, clientLogger, ErrorLogger, logRequestInfo, authLogger, suspiciousLogger, logApiRequest }; +// Middleware pour logger les erreurs 404 +const log404Error = (req, res, next) => { + suspiciousLogger.warn(`404 Not Found: ${req.method} ${req.originalUrl} from IP ${req.ip}`); + next(); +}; + +// Middleware pour logger les erreurs 500 +const log500Error = (error, req, res, next) => { + logSystemError(error, '500 Internal Server Error'); + next(error); +}; + +// Export des fonctionnalités +module.exports = { + logger, + clientLogger, + ErrorLogger, + authLogger, + suspiciousLogger, + apiLogger, + fileSystemLogger, + databaseLogger, + logRequestInfo, + logApiRequest, + logSystemError, + log404Error, + log500Error +}; \ No newline at end of file diff --git a/models/reportManager.js b/models/reportManager.js deleted file mode 100644 index ce60b99..0000000 --- a/models/reportManager.js +++ /dev/null @@ -1,100 +0,0 @@ -const os = require('os'); -const fs = require('fs'); -const path = require('path'); -const ip = require('ip'); -const { logger, logRequestInfo, ErrorLogger } = require('../config/logs'); -const packageJson = require('../package.json'); -const si = require('systeminformation'); -const fetch = require('node-fetch'); - -class SystemReport { - static async generate() { - 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`); - const logs = fs.readFileSync(logFile, 'utf-8'); - - const internalErrors = logs.split('\n').filter(line => /\[38;5;9mInternal-Error/.test(line)); - - if (internalErrors.length === 0) { - logger.info('No internal errors in yesterday\'s logs. No report will be generated.'); - return null; - } - - function 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`; - } - - const osInfo = { - type: os.type(), - platform: os.platform(), - arch: os.arch(), - release: os.release(), - uptime: formatUptime(os.uptime()), - loadavg: os.loadavg().map(load => (load / os.cpus().length) * 100) - }; - - const networkInterfaces = os.networkInterfaces(); - const serverUptime = os.uptime(); - const systemLoad = os.loadavg(); - - 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: ((os.totalmem() - os.freemem()) / os.totalmem() * 100).toFixed(2), - ipAddress: ip.address(), - cdnVersion: packageJson.version, - osInfo: osInfo, - userInfo: userInfo, - errors: internalErrors, - networkInterfaces: networkInterfaces, - serverUptime: serverUptime, - systemLoad: systemLoad, - diskUsage: diskUsage, - cpuTemperature: cpuTemperature, - }; - - const filename = path.join(__dirname, '..', 'report', `report_${previousDate}_${ip.address()}.json`); - fs.writeFileSync(filename, JSON.stringify(systemInfo, null, 2)); - - logger.info("Preparing to send report..."); - logger.info("Report:", JSON.stringify(systemInfo, null, 2)); - - - try { - const response = await fetch('https://cdn-apollon-p198-61m1.dinawo.fr/api/report/receive', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(systemInfo) - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } else { - const data = await response.json(); - logger.info('Report sent successfully. Response data:', data); - } - } catch (error) { - console.error(error); - logger.error('Failed to send report. Error:', error); - } - - return systemInfo; - } -} - -module.exports = SystemReport; diff --git a/package-lock.json b/package-lock.json index e15a6bf..52d567e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cdn-app/insider-myaxrin-labs-dinawo", - "version": "1.0.0-beta.17", + "version": "1.1.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cdn-app/insider-myaxrin-labs-dinawo", - "version": "1.0.0-beta.17", + "version": "1.1.0-beta.1", "license": "ISC", "dependencies": { "@auth/express": "^0.5.1", @@ -256,9 +256,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -454,9 +454,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -493,6 +493,15 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/activedirectory": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/activedirectory/-/activedirectory-0.7.2.tgz", @@ -827,9 +836,9 @@ } }, "node_modules/axios": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz", - "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -1111,16 +1120,44 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.2.tgz", + "integrity": "sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "get-intrinsic": "^1.2.5" }, "engines": { "node": ">= 0.4" @@ -1346,15 +1383,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1515,9 +1543,9 @@ } }, "node_modules/daisyui": { - "version": "4.12.14", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.14.tgz", - "integrity": "sha512-hA27cdBasdwd4/iEjn+aidoCrRroDuo3G5W9NDKaVCJI437Mm/3eSL/2u7MkZ0pt8a+TrYF3aT2pFVemTS3how==", + "version": "4.12.22", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.22.tgz", + "integrity": "sha512-HDLWbmTnXxhE1MrMgSWjVgdRt+bVYHvfNbW3GTsyIokRSqTHonUTrxV3RhpPDjGIWaHt+ELtDCTYCtUFgL2/Nw==", "dev": true, "license": "MIT", "dependencies": { @@ -1547,9 +1575,9 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1726,9 +1754,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -1751,6 +1779,20 @@ "node": ">=0.10" } }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1853,6 +1895,23 @@ "node": ">= 0.6" } }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -1863,13 +1922,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -1883,6 +1939,18 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1920,9 +1988,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -1944,7 +2012,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -1959,6 +2027,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-fileupload": { @@ -2419,16 +2491,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -2483,13 +2560,10 @@ } }, "node_modules/gopd": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.1.0.tgz", - "integrity": "sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" }, @@ -2570,25 +2644,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.1.0.tgz", - "integrity": "sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -2757,9 +2816,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -3036,12 +3095,15 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -3199,6 +3261,15 @@ "semver": "bin/semver.js" } }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3273,9 +3344,9 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3293,6 +3364,15 @@ "node": ">= 0.6" } }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-response": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", @@ -3590,9 +3670,9 @@ } }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3655,9 +3735,9 @@ } }, "node_modules/nodemon": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", - "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", "dev": true, "license": "MIT", "dependencies": { @@ -4036,9 +4116,9 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/pause": { @@ -4320,18 +4400,6 @@ } } }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/postcss-load-config/node_modules/yaml": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", @@ -4654,12 +4722,12 @@ } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.9", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz", + "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==", "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -4913,15 +4981,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -4995,6 +5117,23 @@ "ws": "~8.17.1" } }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -5008,6 +5147,40 @@ "node": ">=10.0.0" } }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5325,9 +5498,9 @@ } }, "node_modules/sweetalert2": { - "version": "11.14.5", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.14.5.tgz", - "integrity": "sha512-8MWk5uc/r6bWhiJWkUXyEuApfXAhSCZT8FFX7pZXL7YwaPxq+9Ynhi2dUzWkOFn9jvLjKj22CXuccZ+IHcnjvQ==", + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.15.0.tgz", + "integrity": "sha512-34Xs0CFBac6I1cGG9d+XaBqJrp0F/0prr8rMYOcU0shU/XmkGkRtlCxWNi7PdKYGw9Qf6aoEHNYicX3au37nkw==", "license": "MIT", "funding": { "type": "individual", @@ -5335,9 +5508,9 @@ } }, "node_modules/systeminformation": { - "version": "5.23.5", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.23.5.tgz", - "integrity": "sha512-PEpJwhRYxZgBCAlWZhWIgfMTjXLqfcaZ1pJsJn9snWNfBW/Z1YQg1mbIUSWrEV3ErAHF7l/OoVLQeaZDlPzkpA==", + "version": "5.23.13", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.23.13.tgz", + "integrity": "sha512-4Cn39sTXp7eN9rV/60aNdCXgpQ5xPcUUPIbwJjqTKj4/bNcSYtgZvFdXvZj6pRHP4h17uk6MfdMP5Fm6AK/b0Q==", "license": "MIT", "os": [ "darwin", @@ -5361,9 +5534,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz", - "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==", + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz", + "integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -5375,7 +5548,7 @@ "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.6", - "lilconfig": "^2.1.0", + "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", diff --git a/package.json b/package.json index f2c801e..8ce3f13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cdn-app/insider-myaxrin-labs-dinawo", - "version": "1.0.0-beta.17", + "version": "1.1.0-beta.1", "description": "", "main": "server.js", "scripts": { diff --git a/public/css/paramadminsettingsetup.styles.css b/public/css/paramadminsettingsetup.styles.css new file mode 100644 index 0000000..c74019b --- /dev/null +++ b/public/css/paramadminsettingsetup.styles.css @@ -0,0 +1,387 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'); + +:root { + /* Light theme */ + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + --radius: 0.5rem; +} + +.dark { + /* Dark theme */ + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; +} + +/* Base styles */ +body { + font-family: 'Inter', sans-serif; + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); + transition: background-color 0.3s ease, color 0.3s ease; +} + +/* Tabs styling */ +.tabs { + display: flex; + gap: 1rem; + border-bottom: 1px solid hsl(var(--border)); + margin-bottom: 2rem; + overflow-x: auto; + padding-bottom: 0.5rem; +} + +.tab { + padding: 0.75rem 1.5rem; + border-radius: var(--radius); + cursor: pointer; + transition: all 0.3s ease; + white-space: nowrap; + display: flex; + align-items: center; + gap: 0.5rem; + background: transparent; + color: hsl(var(--foreground)); + border: none; +} + +.tab:hover { + background-color: hsl(var(--accent)); + color: hsl(var(--accent-foreground)); +} + +.tab.active { + background-color: hsl(var(--primary)); + color: hsl(var(--primary-foreground)); +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; + animation: fadeIn 0.3s ease-out; +} + +/* Cards styling */ +.settings-card { + background-color: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: var(--radius); + padding: 1.5rem; + margin-bottom: 1.5rem; +} + +.settings-card-header { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 1.5rem; +} + +.settings-card-title { + font-size: 1.25rem; + font-weight: 600; + color: hsl(var(--card-foreground)); +} + +/* Form elements */ +.form-group { + margin-bottom: 1.5rem; +} + +.form-control { + width: 100%; + padding: 0.75rem; + border-radius: var(--radius); + border: 1px solid hsl(var(--border)); + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); + transition: border-color 0.3s ease; +} + +.form-control:focus { + outline: none; + border-color: hsl(var(--ring)); + box-shadow: 0 0 0 2px hsl(var(--ring) / 0.2); +} + +/* Switch toggle */ +.switch { + position: relative; + display: inline-block; + width: 3.5rem; + height: 2rem; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: hsl(var(--muted)); + transition: .4s; + border-radius: 2rem; +} + +.slider:before { + position: absolute; + content: ""; + height: 1.5rem; + width: 1.5rem; + left: 0.25rem; + bottom: 0.25rem; + background-color: white; + transition: .4s; + border-radius: 50%; +} + +input:checked + .slider { + background-color: hsl(var(--primary)); +} + +input:checked + .slider:before { + transform: translateX(1.5rem); +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.75rem 1.5rem; + border-radius: var(--radius); + font-weight: 500; + transition: all 0.3s ease; + cursor: pointer; + border: none; +} + +.btn-primary { + background-color: hsl(var(--primary)); + color: hsl(var(--primary-foreground)); +} + +.btn-secondary { + background-color: hsl(var(--secondary)); + color: hsl(var(--secondary-foreground)); +} + +.btn-icon { + padding: 0.5rem; + border-radius: 50%; +} + +/* IP List */ +.ip-entry { + display: flex; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +/* Path List */ +.path-entry { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem 1rem; + background-color: hsl(var(--secondary)); + border-radius: var(--radius); + margin-bottom: 0.5rem; +} + +/* Log Levels */ +.log-level-btn { + padding: 0.5rem 1rem; + border-radius: var(--radius); + background-color: hsl(var(--secondary)); + color: hsl(var(--secondary-foreground)); + transition: all 0.3s ease; +} + +.log-level-btn.active { + background-color: hsl(var(--primary)); + color: hsl(var(--primary-foreground)); +} + +/* Fixed bottom bar */ +.fixed-bottom-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 1rem; + background-color: hsl(var(--background)); + border-top: 1px solid hsl(var(--border)); + z-index: 50; +} + +/* Theme switcher */ +.theme-switcher { + position: fixed; + top: 1rem; + right: 1rem; + padding: 0.75rem; + border-radius: 50%; + background-color: hsl(var(--secondary)); + color: hsl(var(--secondary-foreground)); + cursor: pointer; + transition: all 0.3s ease; + z-index: 50; +} + +.theme-switcher:hover { + background-color: hsl(var(--accent)); +} + +/* Nested settings */ +.nested-settings { + margin-top: 1rem; + padding-left:padding-left: 1.5rem; + border-left: 2px solid hsl(var(--border)); + animation: fadeIn 0.3s ease-out; +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeOut { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(-10px); + } +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .tabs { + flex-wrap: nowrap; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + padding-bottom: 0.5rem; + } + + .tab { + padding: 0.5rem 1rem; + font-size: 0.875rem; + } + + .settings-card { + padding: 1rem; + } + + .fixed-bottom-bar { + padding: 0.75rem; + } + + .btn { + padding: 0.5rem 1rem; + font-size: 0.875rem; + } +} + +/* Dark mode adjustments */ +.dark .form-control { + background-color: hsl(var(--input)); +} + +.dark .settings-card { + background-color: hsl(var(--card)); +} + +/* Utility classes */ +.hidden { + display: none; +} + +.animate { + animation: fadeIn 0.3s ease-out; +} + +.fade-out { + animation: fadeOut 0.3s ease-out forwards; +} + +/* Modal styles */ +.modal-common-paths { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: hsl(var(--background)); + border: 1px solid hsl(var(--border)); + border-radius: var(--radius); + padding: 1.5rem; + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + z-index: 1000; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); +} + +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 999; +} \ No newline at end of file diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 75a65b4..4a9781f 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -725,4 +725,14 @@ document.addEventListener('DOMContentLoaded', async function() { } catch (error) { console.error('Erreur lors du chargement:', error); } +}); + +fetch('/build-metadata') +.then(response => response.json()) +.then(data => { + document.getElementById('version-number').textContent = data.build_version; +}) +.catch(error => { + console.error('Error fetching version:', error); + document.getElementById('version-number').textContent = 'Version indisponible'; }); \ No newline at end of file diff --git a/public/js/folder.js b/public/js/folder.js index 9eb1333..b5cb6ca 100644 --- a/public/js/folder.js +++ b/public/js/folder.js @@ -412,3 +412,14 @@ async function showFileInfo(fileName) { }); }); }); + + // Fetch version from build-metadata API + fetch('/build-metadata') + .then(response => response.json()) + .then(data => { + document.getElementById('version-number').textContent = data.build_version; + }) + .catch(error => { + console.error('Error fetching version:', error); + document.getElementById('version-number').textContent = 'Version indisponible'; + }); \ No newline at end of file diff --git a/public/js/paramadminsettingsetup.script.js b/public/js/paramadminsettingsetup.script.js new file mode 100644 index 0000000..746220f --- /dev/null +++ b/public/js/paramadminsettingsetup.script.js @@ -0,0 +1,682 @@ +const body = document.body; +const themeSwitcher = document.getElementById('themeSwitcher'); + +// Gestion du thème +function setTheme(theme) { + if (theme === 'dark') { + body.classList.add('dark'); + } else { + body.classList.remove('dark'); + } + localStorage.setItem('theme', theme); +} + +const savedTheme = localStorage.getItem('theme'); +if (savedTheme) { + setTheme(savedTheme); +} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + setTheme('dark'); +} + +themeSwitcher.addEventListener('click', function() { + if (body.classList.contains('dark')) { + setTheme('light'); + } else { + setTheme('dark'); + } +}); + +// Gestion des formulaires LDAP et Discord +function toggleForm(formId, checkbox) { + const form = document.getElementById(formId); + form.style.display = checkbox.checked ? 'block' : 'none'; +} + +// Fonctions pour la gestion des IPs +function addIpField() { + const ipList = document.getElementById('ipList'); + const newField = document.createElement('div'); + newField.className = 'flex items-center space-x-2 animate'; + newField.innerHTML = ` + + + `; + ipList.appendChild(newField); +} + +function removeIpField(button) { + const fieldContainer = button.parentElement; + fieldContainer.classList.add('fade-out'); + setTimeout(() => { + fieldContainer.remove(); + }, 300); +} + +// Fonctions de validation IP +function validateIP(ip) { + if (!ip) return false; + + if (ip.includes('/')) { + const [addr, bits] = ip.split('/'); + const bitsNum = parseInt(bits); + + if (isIPv4(addr)) { + return bitsNum >= 0 && bitsNum <= 32; + } else if (isIPv6(addr)) { + return bitsNum >= 0 && bitsNum <= 128; + } + return false; + } + + return isIPv4(ip) || isIPv6(ip); +} + +function isIPv4(ip) { + const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; + if (!ipv4Regex.test(ip)) return false; + + const parts = ip.split('.'); + return parts.every(part => { + const num = parseInt(part); + return num >= 0 && num <= 255; + }); +} + +function isIPv6(ip) { + const ipv6Regex = /^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; + return ipv6Regex.test(ip); +} + +// Gestionnaire pour le switch des logs et les niveaux +document.addEventListener('DOMContentLoaded', function() { + const logsSwitch = document.getElementById('logsEnabled'); + const logsLevelsSection = document.getElementById('logsLevelsSection'); + const logsPathsSection = document.getElementById('logsPathsSection'); + + if (logsSwitch) { + logsSwitch.addEventListener('change', function() { + const isEnabled = this.checked; + + // Activer/désactiver visuellement les sections + if (logsLevelsSection) logsLevelsSection.style.opacity = isEnabled ? '1' : '0.5'; + if (logsPathsSection) logsPathsSection.style.opacity = isEnabled ? '1' : '0.5'; + + if (!isEnabled) { + // Décocher tous les niveaux de log + document.querySelectorAll('input[name="logs[levels][]"]').forEach(checkbox => { + checkbox.checked = false; + checkbox.parentElement.classList.remove('bg-blue-600'); + checkbox.parentElement.classList.add('bg-gray-700'); + }); + } + }); + } +}); + +function toggleLogLevel(button) { + if (!document.getElementById('logsEnabled').checked) { + return; // Ne rien faire si les logs sont désactivés + } + button.classList.toggle('bg-blue-600'); + button.classList.toggle('bg-gray-700'); + const checkbox = button.querySelector('input[type="checkbox"]'); + checkbox.checked = !checkbox.checked; +} + +// Gestion du formulaire principal +async function handleFormSubmit(e) { + e.preventDefault(); + + // Validation des IPs + const ipInputs = document.querySelectorAll('input[name="allowedIps[]"]'); + let hasError = false; + + ipInputs.forEach(input => { + input.classList.remove('ip-error'); + const value = input.value.trim(); + if (value && !validateIP(value)) { + input.classList.add('ip-error'); + hasError = true; + } + }); + + if (hasError) { + Swal.fire({ + icon: 'error', + title: 'Erreur de validation', + text: 'Veuillez vérifier le format des IPs saisies' + }); + return; + } + + try { + const formData = new FormData(this); + const formObject = {}; + + // Traitement des données du formulaire + formData.forEach((value, key) => { + if (key.includes('[') && key.includes(']')) { + const parts = key.split('['); + const mainKey = parts[0]; + const subKey = parts[1].replace(']', ''); + + if (!formObject[mainKey]) { + formObject[mainKey] = {}; + } + + if (key.endsWith('[]')) { + if (!formObject[mainKey][subKey.replace('[]', '')]) { + formObject[mainKey][subKey.replace('[]', '')] = []; + } + formObject[mainKey][subKey.replace('[]', '')].push(value); + } else { + formObject[mainKey][subKey] = value; + } + } else { + formObject[key] = value; + } + }); + + // Gestion de l'état enabled/disabled des logs + formObject.logs.enabled = document.getElementById('logsEnabled')?.checked ? 'on' : 'off'; + + // Si les logs sont désactivés + if (formObject.logs.enabled === 'off') { + formObject.logs = { + enabled: 'off', + levels: [], + excludePaths: [], + includeOnly: [] + }; + } else { + // Si les logs sont activés, on s'assure d'avoir tous les champs + if (!formObject.logs.levels) formObject.logs.levels = []; + if (!formObject.logs.excludePaths) formObject.logs.excludePaths = []; + if (!formObject.logs.includeOnly) formObject.logs.includeOnly = []; + } + + + // Gérer l'état des services + if (!formObject.services) formObject.services = {}; + + // Service de nettoyage de fichiers + formObject.services.fileCleanup = { + enabled: document.getElementById('fileCleanupEnabled')?.checked ? 'on' : 'off', + ...(document.getElementById('fileCleanupEnabled')?.checked && { + schedule: document.getElementById('fileCleanupSchedule')?.value + }) + }; + + // Service de gestion des rapports + formObject.services.reportManager = { + enabled: document.getElementById('reportManagerEnabled')?.checked ? 'on' : 'off', + ...(document.getElementById('reportManagerEnabled')?.checked && { + schedule: document.getElementById('reportSchedule')?.value, + endpoint: document.getElementById('reportEndpoint')?.value + }) + }; + + // Gérer l'état de LDAP + if (!formObject.ldap) formObject.ldap = {}; + formObject.ldap.enabled = document.getElementById('ldapEnabled').checked ? 'on' : 'off'; + if (formObject.ldap.enabled === 'off') { + delete formObject.ldap.url; + delete formObject.ldap.baseDN; + delete formObject.ldap.username; + delete formObject.ldap.password; + } + + // Gérer l'état de Discord + if (!formObject.discord) formObject.discord = {}; + formObject.discord.enabled = document.getElementById('discordEnabled').checked ? 'on' : 'off'; + if (formObject.discord.enabled === 'off') { + delete formObject.discord.clientID; + delete formObject.discord.clientSecret; + delete formObject.discord.identifyURL; + delete formObject.discord.authorizedIDs; + } + + console.log('Form data to be sent:', formObject); + + const response = await fetch('/api/dpanel/dashboard/admin/update-setup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formObject) + }); + + const result = await response.json(); + + if (result.success) { + Swal.fire({ + icon: 'success', + title: 'Configuration mise à jour', + text: result.message + }).then(() => { + window.location.reload(); + }); + } else { + throw new Error(result.message || 'Erreur lors de la mise à jour'); + } + } catch (error) { + console.error('Erreur:', error); + Swal.fire({ + icon: 'error', + title: 'Erreur', + text: error.message || 'Une erreur est survenue lors de la mise à jour' + }); + } +} + +function showCommonPaths(type) { + const paths = [ + '/api/', + '/auth/', + '/public/', + '/build-metadata', + '/attachments/', + '/favicon.ico', + '/dpanel/' + ]; + + const modalContent = ` + + `; + + Swal.fire({ + html: modalContent, + showConfirmButton: false, + showCancelButton: false, + background: '#1a1a1a', + padding: 0 + }); +} + +function addPath(type) { + const input = document.getElementById(`${type}PathInput`); + const value = input.value.trim(); + if (!value) return; + + const list = document.getElementById(`${type}PathsList`); + const div = document.createElement('div'); + div.className = 'flex items-center space-x-2 py-2 px-3 bg-gray-800 rounded-lg animate'; + div.innerHTML = ` + ${value} + + + `; + list.appendChild(div); + input.value = ''; +} + +function addSelectedPaths(type) { + const checkboxes = document.querySelectorAll('.path-option input[type="checkbox"]:checked'); + checkboxes.forEach(checkbox => { + const input = document.getElementById(`${type}PathInput`); + input.value = checkbox.value; + addPath(type); + }); + Swal.close(); +} + +function removePath(button) { + const div = button.closest('div'); + div.classList.add('fade-out'); + setTimeout(() => div.remove(), 300); +} + +// Fonction pour basculer l'affichage du formulaire de service +function toggleService(serviceName) { + const checkbox = document.getElementById(`${serviceName}Enabled`); + const form = document.getElementById(`${serviceName}Form`); + + if (form) { + form.style.display = checkbox.checked ? 'block' : 'none'; + } +} + +// Validation du format cron +function validateCron(schedule) { + const cronPattern = /^(\*|\d{1,2})(\s+(\*|\d{1,2})){4}$/; + return cronPattern.test(schedule); +} + +// Validation de l'URL +function validateUrl(url) { + try { + new URL(url); + return true; + } catch { + return false; + } +} + +// Initialisation au chargement de la page +document.addEventListener('DOMContentLoaded', function() { + const form = document.getElementById('setupForm'); + if (form) { + form.addEventListener('submit', async function(e) { + e.preventDefault(); + + // Validation des IPs + const ipInputs = document.querySelectorAll('input[name="allowedIps[]"]'); + let hasError = false; + + ipInputs.forEach(input => { + input.classList.remove('ip-error'); + const value = input.value.trim(); + if (value && !validateIP(value)) { + input.classList.add('ip-error'); + hasError = true; + } + }); + + if (hasError) { + Swal.fire({ + icon: 'error', + title: 'Erreur de validation', + text: 'Veuillez vérifier le format des IPs saisies' + }); + return; + } + + try { + // Création de l'objet de données + const formObject = { + domain: document.getElementById('domain').value, + webhooks_discord: document.getElementById('webhooksDiscord').value, + + // Configuration LDAP + ldap: { + enabled: document.getElementById('ldapEnabled').checked ? 'on' : 'off' + }, + + // Configuration Discord + discord: { + enabled: document.getElementById('discordEnabled').checked ? 'on' : 'off' + }, + + // Configuration des services + services: { + fileCleanup: { + enabled: document.getElementById('fileCleanupEnabled')?.checked ? 'on' : 'off', + schedule: document.getElementById('fileCleanupSchedule')?.value || '' + }, + reportManager: { + enabled: document.getElementById('reportManagerEnabled')?.checked ? 'on' : 'off', + schedule: document.getElementById('reportSchedule')?.value || '', + endpoint: document.getElementById('reportEndpoint')?.value || '' + } + }, + + // IPs autorisées + allowedIps: Array.from(document.querySelectorAll('input[name="allowedIps[]"]')) + .map(input => input.value.trim()) + .filter(value => value !== '') + }; + + // Ajout des champs LDAP si activé + if (formObject.ldap.enabled === 'on') { + formObject.ldap.url = document.getElementById('ldapUrl').value; + formObject.ldap.baseDN = document.getElementById('ldapBaseDN').value; + formObject.ldap.username = document.getElementById('ldapUsername').value; + formObject.ldap.password = document.getElementById('ldapPassword').value; + } + + // Ajout des champs Discord si activé + if (formObject.discord.enabled === 'on') { + formObject.discord.clientID = document.getElementById('discordClientID').value; + formObject.discord.clientSecret = document.getElementById('discordClientSecret').value; + formObject.discord.authorizedIDs = document.getElementById('discordAuthorizedIDs').value; + } + + // Gestion des logs + formObject.logs = { + enabled: document.getElementById('logsEnabled')?.checked ? 'on' : 'off', + levels: [], + excludePaths: [], + includeOnly: [] + }; + + // Si les logs sont activés, ajout des configurations supplémentaires + if (formObject.logs.enabled === 'on') { + // Récupération des niveaux de logs sélectionnés + formObject.logs.levels = Array.from(document.querySelectorAll('input[name="logs[levels][]"]:checked')) + .map(input => input.value); + + // Récupération des chemins exclus + formObject.logs.excludePaths = Array.from(document.querySelectorAll('input[name="logs[excludePaths][]"]')) + .map(input => input.value.trim()) + .filter(value => value !== ''); + + // Récupération des chemins inclus + formObject.logs.includeOnly = Array.from(document.querySelectorAll('input[name="logs[includeOnly][]"]')) + .map(input => input.value.trim()) + .filter(value => value !== ''); + } + + console.log('Données à envoyer:', formObject); + + const response = await fetch('/api/dpanel/dashboard/admin/update-setup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formObject) + }); + + const result = await response.json(); + + if (result.success) { + Swal.fire({ + icon: 'success', + title: 'Configuration mise à jour', + text: result.message || 'Configuration sauvegardée avec succès' + }).then(() => { + window.location.reload(); + }); + } else { + throw new Error(result.message || 'Erreur lors de la mise à jour'); + } + } catch (error) { + console.error('Erreur:', error); + Swal.fire({ + icon: 'error', + title: 'Erreur', + text: error.message || 'Une erreur est survenue lors de la mise à jour' + }); + } + }); + } +}); + +// Fonctions de validation +function validateCron(cron) { + const cronRegex = /^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])) (\*|([0-6]))$/; + return cronRegex.test(cron.trim()); +} + +function validateUrl(url) { + try { + new URL(url); + return true; + } catch { + return false; + } +} + +// Gestion des onglets +document.addEventListener('DOMContentLoaded', function() { + // Init des onglets + const tabs = document.querySelectorAll('.tab'); + tabs.forEach(tab => { + tab.addEventListener('click', () => switchTab(tab)); + }); + + // Init du thème + initTheme(); + + // Init du formulaire + initForm(); +}); + +// Gestion du thème +function initTheme() { + const themeSwitcher = document.getElementById('themeSwitcher'); + const savedTheme = localStorage.getItem('theme') || 'light'; + document.documentElement.classList.toggle('dark', savedTheme === 'dark'); + updateThemeIcon(savedTheme); + + themeSwitcher.addEventListener('click', toggleTheme); +} + +function toggleTheme() { + const isDark = document.documentElement.classList.toggle('dark'); + localStorage.setItem('theme', isDark ? 'dark' : 'light'); + updateThemeIcon(isDark ? 'dark' : 'light'); +} + +function updateThemeIcon(theme) { + const icon = document.querySelector('#themeSwitcher i'); + icon.className = theme === 'dark' ? 'fas fa-moon' : 'fas fa-sun'; +} + +// Gestion des onglets +function switchTab(selectedTab) { + const tabs = document.querySelectorAll('.tab'); + const contents = document.querySelectorAll('.tab-content'); + + tabs.forEach(tab => tab.classList.remove('active')); + contents.forEach(content => content.classList.remove('active')); + + selectedTab.classList.add('active'); + const targetContent = document.querySelector(`[data-tab-content="${selectedTab.dataset.tab}"]`); + targetContent.classList.add('active'); +} + +// Gestion des sections toggle +function toggleSection(sectionId, checkbox) { + const section = document.getElementById(sectionId); + if (section) { + section.style.display = checkbox.checked ? 'block' : 'none'; + if (checkbox.checked) { + section.classList.add('animate'); + } else { + section.classList.add('fade-out'); + setTimeout(() => { + section.classList.remove('fade-out'); + section.style.display = 'none'; + }, 300); + } + } +} + +// Gestion des IPs +function addIp() { + const ipList = document.getElementById('ipList'); + const newEntry = document.createElement('div'); + newEntry.className = 'ip-entry flex items-center gap-2 animate'; + newEntry.innerHTML = ` + + + `; + ipList.appendChild(newEntry); +} + +function removeIp(button) { + const entry = button.closest('.ip-entry'); + entry.classList.add('fade-out'); + setTimeout(() => entry.remove(), 300); +} + +// Gestion des chemins +function addPath(type) { + const input = document.getElementById(`${type}PathInput`); + const path = input.value.trim(); + if (!path) return; + + const list = document.getElementById(`${type}PathsList`); + const newEntry = document.createElement('div'); + newEntry.className = 'path-entry animate'; + newEntry.innerHTML = ` + ${path} + + + `; + list.appendChild(newEntry); + input.value = ''; +} + +function removePath(button) { + const entry = button.closest('.path-entry'); + entry.classList.add('fade-out'); + setTimeout(() => entry.remove(), 300); +} + +// Gestion des niveaux de log +function toggleLogLevel(button) { + button.classList.toggle('active'); + const checkbox = button.querySelector('input[type="checkbox"]'); + checkbox.checked = !checkbox.checked; +} + +// Chemins communs +function showCommonPaths(type) { + const commonPaths = [ + '/api', + '/auth', + '/public', + '/uploads', + '/static', + '/assets', + '/images', + '/temp' + ]; + + Swal.fire({ + title: 'Chemins communs', + html: ` + + `, + showConfirmButton: false, + customClass: { + popup: 'swal2-dark' + } + }); +} + +function selectCommonPath(path, type) { + const input = document.getElementById(`${type}PathInput`); + input.value = path; + Swal.close(); +} diff --git a/routes/Dpanel/API/Update-Setup-Admin.js b/routes/Dpanel/API/Update-Setup-Admin.js index 0120543..cee7f20 100644 --- a/routes/Dpanel/API/Update-Setup-Admin.js +++ b/routes/Dpanel/API/Update-Setup-Admin.js @@ -2,20 +2,20 @@ const express = require('express'); const fs = require('fs'); const path = require('path'); const router = express.Router(); -const fileUpload = require('express-fileupload'); -const authMiddleware = require('../../../Middlewares/authMiddleware'); const bodyParser = require('body-parser'); -const crypto = require('crypto'); -const os = require('os'); -const osUtils = require('os-utils'); -const Convert = require('ansi-to-html'); -const convert = new Convert(); -const { getUserData, getSetupData } = require('../../../Middlewares/watcherMiddleware'); +const { logger } = require('../../../config/logs'); + +// Utilitaires pour la gestion des services +const services = { + fileCleanup: require('../../../services/fileCleanupService'), + reportManager: require('../../../services/reportService') +}; router.use(bodyParser.json()); +// Fonction de nettoyage des objets function clean(obj) { - for (var propName in obj) { + for (let propName in obj) { if (obj[propName] === null || obj[propName] === undefined || obj[propName] === '') { delete obj[propName]; } else if (typeof obj[propName] === 'object') { @@ -27,6 +27,7 @@ function clean(obj) { } } +// Validation des IPs function validateIP(ip) { if (!ip) return false; @@ -46,9 +47,9 @@ function validateIP(ip) { } function isIPv4(ip) { - const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; + const ipv4Regex = /^\d{1,3}(\.\d{1,3}){3}$/; if (!ipv4Regex.test(ip)) return false; - + const parts = ip.split('.'); return parts.every(part => { const num = parseInt(part); @@ -61,42 +62,111 @@ function isIPv6(ip) { return ipv6Regex.test(ip); } -router.get('/', (req, res) => { - res.status(400).json({ error: 'Bad Request. The request cannot be fulfilled due to bad syntax or missing parameters.' }); -}); +// Fonction pour activer ou désactiver un service +async function handleServices(newConfig, oldConfig) { + if (!newConfig.services) return; -router.post('/', authMiddleware, async (req, res) => { - try { - let setup = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../data', 'setup.json'), 'utf-8')); - - if (req.body.allowedIps) { - const ipsArray = Array.isArray(req.body.allowedIps) ? req.body.allowedIps : [req.body.allowedIps]; - - req.body.allowedIps = ipsArray - .filter(ip => ip && ip.trim()) - .filter(ip => validateIP(ip.trim())) - .map(ip => ip.trim()); - - console.log('IPs validées:', req.body.allowedIps); + for (const [serviceName, serviceConfig] of Object.entries(newConfig.services)) { + const service = services[serviceName]; + if (!service) { + logger.warn(`Service ${serviceName} not found`); + continue; } - clean(req.body); - setup[0] = { - ...setup[0], - ...req.body - }; + const oldServiceConfig = oldConfig?.services?.[serviceName] || {}; + const wasEnabled = oldServiceConfig.enabled === 'on'; // ancien état + const isEnabled = serviceConfig.enabled === 'on'; // nouvel état - fs.writeFileSync( - path.join(__dirname, '../../../data', 'setup.json'), - JSON.stringify(setup, null, 2), - 'utf-8' - ); + try { + logger.info(`Processing service ${serviceName}: wasEnabled=${wasEnabled}, isEnabled=${isEnabled}`); - res.redirect('/dpanel/dashboard/admin/settingsetup'); - } catch (err) { - console.error('Erreur lors de la mise à jour de la configuration:', err); - res.status(500).send('Server Error'); + if (isEnabled && !wasEnabled) { + await service.updateConfig(serviceConfig); + await service.start(); + } else if (!isEnabled && wasEnabled) { + await service.stop(); + } else if (isEnabled && wasEnabled && + JSON.stringify(serviceConfig) !== JSON.stringify(oldServiceConfig)) { + await service.stop(); + await service.updateConfig(serviceConfig); + await service.start(); + } + } catch (error) { + logger.error(`Error handling service ${serviceName}:`, error); + throw error; + } + } +} + +// Mise à jour de la configuration et gestion des services +async function handleServicesUpdate(newConfig, oldConfig) { + if (!newConfig.services) return; + + for (const [serviceName, serviceConfig] of Object.entries(newConfig.services)) { + const oldServiceConfig = oldConfig?.services?.[serviceName] || {}; + await handleServices(serviceName, serviceConfig, oldServiceConfig); + } +} + +// Route principale pour mettre à jour la configuration +router.post('/', async (req, res) => { + try { + const setupPath = path.join(__dirname, '../../../data', 'setup.json'); + const oldConfig = JSON.parse(fs.readFileSync(setupPath, 'utf-8'))[0]; + + // Validation et traitement des IPs autorisées + if (req.body.allowedIps) { + let ipsToProcess = Array.isArray(req.body.allowedIps) ? req.body.allowedIps : req.body.allowedIps[""] || []; + req.body.allowedIps = ipsToProcess.filter(ip => validateIP(ip.trim())).map(ip => ip.trim()); + } + + ['ldap', 'discord'].forEach(service => { + if (req.body[service] && !req.body[service].enabled) { + req.body[service] = { enabled: 'off' }; + } + }); + + // Traitement des services + if (req.body.services) { + for (const [key, value] of Object.entries(req.body.services)) { + if (typeof value === 'string') { + req.body.services[key] = { enabled: 'off', schedule: value }; // Par défaut 'off' et ajouter un schedule si nécessaire + } + + // Si l'état n'est pas 'on' ou 'off', on le met à 'off' + if (value.enabled !== 'on' && value.enabled !== 'off') { + req.body.services[key].enabled = 'off'; + } + } + } + + + // Traitement des logs + if (req.body.logs) { + req.body.logs = { + enabled: req.body.logs.enabled || "on", + excludePaths: Array.isArray(req.body.logs.excludePaths) ? req.body.logs.excludePaths : [], + includeOnly: Array.isArray(req.body.logs.includeOnly) ? req.body.logs.includeOnly : [], + levels: Array.isArray(req.body.logs.levels) ? req.body.logs.levels : [] + }; + } + + // Fusion des anciennes et nouvelles configurations + const newConfig = { ...oldConfig, ...req.body }; + clean(newConfig); + + // Mise à jour des services + await handleServicesUpdate(newConfig, oldConfig); + + // Sauvegarde de la nouvelle configuration + fs.writeFileSync(setupPath, JSON.stringify([newConfig], null, 2)); + + res.status(200).json({ message: 'Configuration mise à jour avec succès.' }); + } catch (error) { + logger.error('Erreur lors de la mise à jour de la configuration:', error); + res.status(500).json({ error: 'Erreur interne du serveur.' }); } }); -module.exports = router; \ No newline at end of file + +module.exports = router; diff --git a/routes/Dpanel/API/Upload.js b/routes/Dpanel/API/Upload.js index ca29ef9..f936433 100644 --- a/routes/Dpanel/API/Upload.js +++ b/routes/Dpanel/API/Upload.js @@ -3,6 +3,7 @@ const fs = require('fs'); const path = require('path'); const multiparty = require('multiparty'); const router = express.Router(); +const bcrypt = require('bcrypt'); // Limite de taille de fichier à 10 Go const MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024; // 10 Go @@ -19,7 +20,7 @@ router.post('/', (req, res) => { maxFilesSize: MAX_FILE_SIZE, }); - form.parse(req, (err, fields, files) => { + form.parse(req, async (err, fields, files) => { if (err) { console.error('Error parsing the file:', err); return res.status(400).send('Error during the file upload'); @@ -30,12 +31,20 @@ router.post('/', (req, res) => { } const file = files.file[0]; - // Modifier le chemin pour être relatif à la racine const userDir = path.join(process.cwd(), 'cdn-files', req.user.name); - - // Utiliser le nom sécurisé fourni par le client const filename = fields.filename ? fields.filename[0] : file.originalFilename; const filePath = path.join(userDir, filename); + + // Récupérer les champs supplémentaires + const expiryDate = fields.expiryDate ? fields.expiryDate[0] : ''; + const password = fields.password ? fields.password[0] : ''; + + // Hasher le mot de passe si présent + const saltRounds = 10; + let hashedPassword = ''; + if (password) { + hashedPassword = await bcrypt.hash(password, saltRounds); + } // Crée le répertoire s'il n'existe pas if (!fs.existsSync(userDir)) { @@ -48,7 +57,7 @@ router.post('/', (req, res) => { readStream.pipe(writeStream); - readStream.on('end', () => { + readStream.on('end', async () => { // Supprimer le fichier temporaire fs.unlinkSync(file.path); @@ -57,6 +66,35 @@ router.post('/', (req, res) => { if (!fileNamePattern.test(filename)) { console.warn('Le fichier uploadé ne suit pas le format de nom sécurisé attendu:', filename); } + + // Mettre à jour file_info.json si password ou expiryDate présent + if (expiryDate || password) { + const fileInfo = { + fileName: filename, + expiryDate: expiryDate, + password: hashedPassword, + Id: req.user.id, + path: filePath + }; + + try { + let data = []; + const fileInfoPath = path.join(__dirname, '../../../data', 'file_info.json'); + + if (fs.existsSync(fileInfoPath)) { + const existingData = await fs.promises.readFile(fileInfoPath, 'utf8'); + data = JSON.parse(existingData); + if (!Array.isArray(data)) { + data = []; + } + } + + data.push(fileInfo); + await fs.promises.writeFile(fileInfoPath, JSON.stringify(data, null, 2)); + } catch (error) { + console.error('Error updating file_info.json:', error); + } + } res.status(200).send({ message: 'File uploaded successfully.', diff --git a/routes/Dpanel/Dashboard/index.js b/routes/Dpanel/Dashboard/index.js index 46fdbf6..15c6af2 100644 --- a/routes/Dpanel/Dashboard/index.js +++ b/routes/Dpanel/Dashboard/index.js @@ -94,7 +94,7 @@ router.get('/', authMiddleware, async (req, res) => { const availableExtensions = Array.from(new Set(fileDetails.map(file => file.extension))); - res.render('dashboard', { files: fileDetails, folders, extensions: availableExtensions, allFolders: folders, folderName: folderName, fileInfoNames: fileInfoNames }); + res.render('dashboard', { files: fileDetails, folders, extensions: availableExtensions, allFolders: folders, folderName: folderName, fileInfoNames: fileInfoNames, userData: userData }); } catch (err) { console.error('Error reading directory:', err); return res.render('error-recovery-file', { error: err.message }); diff --git a/server.js b/server.js index dd9ec57..0bba274 100644 --- a/server.js +++ b/server.js @@ -1,151 +1,176 @@ +require('dotenv').config(); const express = require('express'); const session = require('express-session'); const passport = require('passport'); const bodyParser = require('body-parser'); -const { logger, logRequestInfo, ErrorLogger } = require('./config/logs'); -const path = require("path"); -const { version } = require('./package.json'); -const axios = require('axios'); +const path = require('path'); const flash = require('connect-flash'); const crypto = require('crypto'); -const fs = require('fs'); -const SystemReport = require('./models/reportManager.js'); -const routes = require('./routes/routes.js'); +const fs = require('fs').promises; const cron = require('node-cron'); const chalk = require('chalk'); -require('dotenv').config(); +// Import des services et configurations +const { logger, logRequestInfo, ErrorLogger } = require('./config/logs'); +const { version } = require('./package.json'); +const routes = require('./routes/routes.js'); +const fileCleanup = require('./services/fileCleanupService'); +const reportManager = require('./services/reportService.js'); + +// Configuration de l'application const app = express(); +const PORT = process.env.PORT || 5053; -app.set('trust proxy', 1); - +// Import des modèles requis require('./models/fileCreated.js'); -let setup; -try { - setup = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'setup.json'), 'utf8')); -} catch (err) { - console.error('Error reading setup.json:', err); - setup = {}; -} - -if (setup.discord !== undefined) { - require('./models/Passport-Discord.js'); -} - -if (setup.ldap !== undefined) { - require('./models/Passport-ActiveDirectory.js'); -} - -app.use(express.static(path.join(__dirname, 'public'))); - -app.get(['/data/user.json', '/data/file_info.json', '/data/setup.json'], (req, res) => { - res.status(403).json({ error: 'Access Denied. You do not have permission to access this resource.' }); -}); - -app.use(express.urlencoded({ extended: true })); - -function generateSecretKey() { - return crypto.randomBytes(64).toString('hex'); -} - -app.use(session({ - secret: generateSecretKey(), - resave: false, - saveUninitialized: true, - cookie: { secure: false } -})); - -app.use(passport.initialize()); -app.use(passport.session()); - -app.use(bodyParser.urlencoded({ extended: true })); -app.use(bodyParser.json()); -app.use(flash()); - -app.use('/public', express.static(path.join(__dirname, 'public'))); -app.set('view engine', 'ejs'); -app.set('views', __dirname + '/views'); -app.use(routes); - -app.use(logRequestInfo); - -cron.schedule('00 03 * * *', async () => { +// Lecture du fichier de configuration +const loadSetup = async () => { try { - const report = await SystemReport.generate(); - if (report !== null) { - logger.info('System error report generated successfully'); - } + const setupPath = path.join(__dirname, 'data', 'setup.json'); + const setupData = await fs.readFile(setupPath, 'utf8'); + return JSON.parse(setupData); } catch (err) { - ErrorLogger.error('Error generating report :', err); + logger.error('Error reading setup.json:', err); + return {}; } -}); +}; -cron.schedule('0 * * * *', async () => { - try { - const fileInfoData = await fs.promises.readFile(path.join(__dirname, '/data/', 'file_info.json'), 'utf8'); - const fileInfo = JSON.parse(fileInfoData); +// Configuration de l'application +const configureApp = async () => { + const setup = await loadSetup(); - const now = new Date(); + // Configuration des stratégies d'authentification + if (setup.discord) require('./models/Passport-Discord.js'); + if (setup.ldap) require('./models/Passport-ActiveDirectory.js'); - for (let index = fileInfo.length - 1; index >= 0; index--) { - const file = fileInfo[index]; - const expiry = new Date(file.expiryDate); + // Middleware de base + app.set('trust proxy', 1); + app.set('view engine', 'ejs'); + app.set('views', path.join(__dirname, 'views')); - if ((file.expiryDate && expiry <= now) || !(await fileExists(file.path))) { - fileInfo.splice(index, 1); - } + // Gestionnaire de favicon.ico + app.get('/favicon.ico', (req, res) => { + res.status(204).end(); + }); + + // Protection des fichiers sensibles + app.get(['/data/user.json', '/data/file_info.json', '/data/setup.json'], + (req, res) => res.status(403).json({ error: 'Access Denied' })); + + // Configuration des middlewares + app.use(express.static(path.join(__dirname, 'public'))); + app.use('/public', express.static(path.join(__dirname, 'public'))); + app.use(express.urlencoded({ extended: true })); + app.use(bodyParser.json()); + app.use(session({ + secret: crypto.randomBytes(64).toString('hex'), + resave: false, + saveUninitialized: true, + cookie: { + secure: process.env.NODE_ENV === 'production', + maxAge: 24 * 60 * 60 * 1000 } + })); + app.use(passport.initialize()); + app.use(passport.session()); + app.use(flash()); + app.use(logRequestInfo); + app.use(routes); - logger.info('Successfully checked file expirations and updated file_info.json'); - } catch (err) { - ErrorLogger.error(`Failed to check file expirations: ${err}`); - } -}); - -async function fileExists(filePath) { - try { - await fs.promises.access(filePath); - return true; - } catch { - return false; - } -} - -function getAllFiles(dirPath, arrayOfFiles) { - const files = fs.readdirSync(dirPath); - - arrayOfFiles = arrayOfFiles || []; - - files.forEach(function(file) { - if (fs.statSync(dirPath + "/" + file).isDirectory()) { - arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles); + app.use((req, res, next) => { + if (req.accepts('html')) { + res.status(404).render('unauthorized', { url: req.url }); } else { - arrayOfFiles.push(path.join(dirPath, "/", file)); + // Pour les requêtes API ou autres + res.status(404).json({ error: 'Not Found' }); } }); - return arrayOfFiles; -} + app.use((err, req, res, next) => { + ErrorLogger.error('Unhandled error:', err); + + res.status(500).json(response); + }); +}; -const allFiles = getAllFiles(__dirname); +process.removeAllListeners('warning'); +process.on('warning', (warning) => { + if (warning.name === 'DeprecationWarning' && warning.message.includes('punycode')) { + return; + } + console.warn(warning.name, warning.message); +}); -const SERVER = process.env.PORT || 5053; -app.listen(SERVER, () => { - SERVER.timeout = 300000 - allFiles.forEach(file => { +// Configuration des tâches planifiées +const configureCronJobs = () => { - console.log(`[ ${chalk.green('OK')} ] Loaded file: ${file}`); + // Service de nettoyage des fichiers + fileCleanup.start(); + + // Service de rreport système + reportManager.start(); +}; + +// Utilitaires pour la vérification des fichiers +const fileUtils = { + async fileExists(filePath) { + try { + await fs.access(filePath); + return true; + } catch { + return false; + } + } +}; + +// Démarrage du serveur +const startServer = () => { + const server = app.listen(PORT, () => { + server.timeout = 300000; + console.clear() + + if (logger) { + logger.info('☀️ Welcome to the Content Delivery Network Server'); + logger.info(`🚀 Server running on port ${PORT}`); + logger.info(`⚜️ Application developed by Dinawo, part of the Myaxrin Labs group`); + logger.info(`♨️ Version: ${version}`); + } else { + console.error('🔴 Logger is not initialized'); + } }); - console.clear(); - if (logger) { - logger.info(`☀️ Welcome to the Content Delivery Network Server`); - logger.info(`🚀 Your server is available and running on port ${SERVER}`); - logger.info(`⚜️ Application developed by Dinawo, part of the Myaxrin Labs group`); - logger.info(`♨️ Version: ${version}`); - console.log(''); - } else { - console.error('🔴 Logger is not initialized'); + return server; +}; + +// Gestion des erreurs globales +const configureErrorHandling = () => { + process.on('uncaughtException', (err) => { + ErrorLogger.error('Uncaught Exception:', err); + }); + + process.on('unhandledRejection', (reason, promise) => { + ErrorLogger.error('Unhandled Rejection at:', promise, 'reason:', reason); + }); +}; + +// Initialisation de l'application +const initializeApp = async () => { + try { + configureErrorHandling(); + await configureApp(); + configureCronJobs(); + const server = startServer(); + + return server; + } catch (err) { + logger.error('Failed to initialize application:', err); + process.exit(1); } -}); +}; + +// Démarrage de l'application +initializeApp(); + +// Export pour les tests +module.exports = { app, fileUtils }; \ No newline at end of file diff --git a/services/BaseService.js b/services/BaseService.js new file mode 100644 index 0000000..811563c --- /dev/null +++ b/services/BaseService.js @@ -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; \ No newline at end of file diff --git a/services/fileCleanupService.js b/services/fileCleanupService.js new file mode 100644 index 0000000..e19a5f2 --- /dev/null +++ b/services/fileCleanupService.js @@ -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(); \ No newline at end of file diff --git a/services/reportService.js b/services/reportService.js new file mode 100644 index 0000000..3aca999 --- /dev/null +++ b/services/reportService.js @@ -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(); \ No newline at end of file diff --git a/views/dashboard.ejs b/views/dashboard.ejs index 2c3b393..78179cd 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -69,14 +69,25 @@ @@ -152,7 +163,7 @@ diff --git a/views/folder.ejs b/views/folder.ejs index 510ffbe..4cba9b0 100644 --- a/views/folder.ejs +++ b/views/folder.ejs @@ -190,10 +190,10 @@
diff --git a/views/paramAdminSettingSetup.ejs b/views/paramAdminSettingSetup.ejs index c69aaef..3944e04 100644 --- a/views/paramAdminSettingSetup.ejs +++ b/views/paramAdminSettingSetup.ejs @@ -4,12 +4,20 @@ Parameter Admin + + - + + -
-
-

Gestion de la configuration

+
+
+ +
+

Gestion de la configuration

+

Gérez les paramètres et la configuration de votre application

+
-
-
-

Paramètres LDAP

-
- - + +
+ + + + + +
+ + + +
+
+
+ +

Configuration du domaine

+
+
+ + +
+
+ + +
-
-
- - +
+ + +
+ +
+
+ +

Configuration LDAP

-
- - +
+ +
-
- - -
-
- - +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
-

Paramètres Discord

-
- - + +
+
+ +

Configuration Discord

+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
-
-
- - +
+ + +
+ +
+
+ +

Service de nettoyage des fichiers

-
- - +
+
+ +

Nettoie automatiquement les fichiers temporaires

+
+
-
- - -
-
- - +
+
+ + +
-

Autres Paramètres

-
- - -
-
- - + +
+
+ +

Gestionnaire de rapports

+
+
+
+ +

Génère des rapports périodiques

+
+ +
+
+
+ + +
+
+ + +
+
+
-
- -
+ +
+
+
+ +

IPs Autorisées

+
+
- <% if (setup.allowedIps && setup.allowedIps.length > 0) { %> - <% setup.allowedIps.forEach((ip, index) => { %> -
+ <% if (setup.allowedIps?.length > 0) { %> + <% setup.allowedIps.forEach(ip => { %> +
-
<% }) %> <% } else { %> -
- -
<% } %>
-
+
- + <% }) %> +
+
+ + +
+ +
+
+ + +
+ +
+
+ <% if (setup.logs?.excludePaths?.length > 0) { %> + <% setup.logs.excludePaths.forEach(path => { %> +
+ <%= path %> + + +
+ <% }) %> + <% } %> +
+
+ + +
+ +
+
+ + +
+ +
+
+ <% if (setup.logs?.includeOnly?.length > 0) { %> + <% setup.logs.includeOnly.forEach(path => { %> +
+ <%= path %> + + +
+ <% }) %> + <% } %> +
+
+
+
+
+ + + +
+
+ + + Retour + + - - - - - Retour au tableau de bord admin - +
- - + \ No newline at end of file diff --git a/views/promote.ejs b/views/promote.ejs index 5bc6517..74e51ca 100644 --- a/views/promote.ejs +++ b/views/promote.ejs @@ -152,9 +152,8 @@
-
-

Version: 1.0.0-beta.17 | © 2024 Myaxrin Labs

+

Version: ... | © 2024 Myaxrin Labs

\ No newline at end of file