From 51d11a6c368ec3dc0bfef31a2192d2f703075d4b Mon Sep 17 00:00:00 2001
From: Dinawo
Date: Sat, 21 Dec 2024 18:16:25 +0100
Subject: [PATCH] Update v1.1.0-beta.1
---
.drone.yml | 8 +-
config/logs.js | 299 ++++----
models/reportManager.js | 100 ---
package-lock.json | 425 +++++++----
package.json | 2 +-
public/css/paramadminsettingsetup.styles.css | 387 ++++++++++
public/js/dashboard.js | 10 +
public/js/folder.js | 11 +
public/js/paramadminsettingsetup.script.js | 682 +++++++++++++++++
routes/Dpanel/API/Update-Setup-Admin.js | 154 ++--
routes/Dpanel/API/Upload.js | 48 +-
routes/Dpanel/Dashboard/index.js | 2 +-
server.js | 263 ++++---
services/BaseService.js | 131 ++++
services/fileCleanupService.js | 141 ++++
services/reportService.js | 181 +++++
views/dashboard.ejs | 19 +-
views/folder.ejs | 8 +-
views/paramAdminSettingSetup.ejs | 738 +++++++++----------
views/promote.ejs | 14 +-
20 files changed, 2688 insertions(+), 935 deletions(-)
delete mode 100644 models/reportManager.js
create mode 100644 public/css/paramadminsettingsetup.styles.css
create mode 100644 public/js/paramadminsettingsetup.script.js
create mode 100644 services/BaseService.js
create mode 100644 services/fileCleanupService.js
create mode 100644 services/reportService.js
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 = `
+
+
Chemins communs
+
+ ${paths.map(path => `
+
+
+
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+
+ 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: `
+
+ ${commonPaths.map(path => `
+
+ ${path}
+
+ `).join('')}
+
+ `,
+ 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 @@
-
-
-
Gestion de la configuration
+
+
+
+
+
Gestion de la configuration
+
Gérez les paramètres et la configuration de votre application
+
-