Update v1.1.0-beta.1
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2024-12-21 18:16:25 +01:00
parent f7658eca22
commit 51d11a6c36
20 changed files with 2688 additions and 935 deletions

View File

@@ -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

View File

@@ -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,
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
maxFiles: '14d',
format: format.combine(
format.timestamp(),
format.json()
)
});
};
const logger = winston.createLogger({
format: format.combine(
format.label({ label: 'server' }),
// 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, level, label, message } = info;
const { timestamp, label, message } = info;
const prefix = logPrefix(label);
return `${prefix} [\x1b[36m${timestamp}\x1b[0m] ${message}`;
})
),
);
};
// Création des différents loggers
const createLogger = (label) => {
return winston.createLogger({
format: createLoggerFormat(label),
transports: [
new winston.transports.Console(),
createDailyRotateFileTransport()
]
});
};
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()
]
});
// 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 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()
]
});
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) => {
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' };
// 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'];
req.log = clientLogger;
req.log.info(`[${ip}] - ${userAgent} - ${req.method} ${req.url}`);
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();
};
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()
]
});
}
// Middleware spécifique pour les requêtes API
const logApiRequest = (req, res, 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
};

View File

@@ -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;

425
package-lock.json generated
View File

@@ -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",

View File

@@ -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": {

View File

@@ -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;
}

View File

@@ -726,3 +726,13 @@ document.addEventListener('DOMContentLoaded', async function() {
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';
});

View File

@@ -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';
});

View File

@@ -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 = `
<input type="text" name="allowedIps[]" class="form-control flex-1"
placeholder="ex: 10.20.0.34 ou 10.20.0.0/24">
<button type="button" class="btn btn-secondary p-2" onclick="removeIpField(this)">
<i class="fas fa-trash"></i>
</button>
`;
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 = `
<div class="modal-common-paths">
<h3 class="text-xl mb-4">Chemins communs</h3>
<div class="space-y-2">
${paths.map(path => `
<div class="path-option">
<input type="checkbox" id="path-${path}" value="${path}" class="mr-3">
<label for="path-${path}" class="flex-1">${path}</label>
</div>
`).join('')}
</div>
<div class="flex justify-end gap-2 mt-4">
<button onclick="Swal.close()" class="px-4 py-2 bg-gray-600 rounded">Annuler</button>
<button onclick="addSelectedPaths('${type}')" class="px-4 py-2 bg-blue-600 rounded">Ajouter</button>
</div>
</div>
`;
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 = `
<span class="flex-1">${value}</span>
<button type="button" onclick="removePath(this)" class="text-gray-400 hover:text-red-500">
<i class="fas fa-times"></i>
</button>
<input type="hidden" name="logs[${type === 'exclude' ? 'excludePaths' : 'includeOnly'}][]" value="${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 = `
<input type="text" name="allowedIps[]" class="form-control flex-1" placeholder="IPv4/IPv6 ou CIDR">
<button type="button" class="btn btn-icon" onclick="removeIp(this)">
<i class="fas fa-trash"></i>
</button>
`;
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 = `
<span>${path}</span>
<button type="button" onclick="removePath(this)" class="text-gray-400 hover:text-red-500 transition-colors">
<i class="fas fa-times"></i>
</button>
<input type="hidden" name="logs[${type}Paths][]" value="${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: `
<div class="modal-common-paths">
${commonPaths.map(path => `
<div class="path-option" onclick="selectCommonPath('${path}', '${type}')">
${path}
</div>
`).join('')}
</div>
`,
showConfirmButton: false,
customClass: {
popup: 'swal2-dark'
}
});
}
function selectCommonPath(path, type) {
const input = document.getElementById(`${type}PathInput`);
input.value = path;
Swal.close();
}

View File

@@ -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,7 +47,7 @@ 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('.');
@@ -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;
for (const [serviceName, serviceConfig] of Object.entries(newConfig.services)) {
const service = services[serviceName];
if (!service) {
logger.warn(`Service ${serviceName} not found`);
continue;
}
const oldServiceConfig = oldConfig?.services?.[serviceName] || {};
const wasEnabled = oldServiceConfig.enabled === 'on'; // ancien état
const isEnabled = serviceConfig.enabled === 'on'; // nouvel état
router.post('/', authMiddleware, async (req, res) => {
try {
let setup = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../data', 'setup.json'), 'utf-8'));
logger.info(`Processing service ${serviceName}: wasEnabled=${wasEnabled}, isEnabled=${isEnabled}`);
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);
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;
}
}
}
clean(req.body);
setup[0] = {
...setup[0],
...req.body
};
// Mise à jour de la configuration et gestion des services
async function handleServicesUpdate(newConfig, oldConfig) {
if (!newConfig.services) return;
fs.writeFileSync(
path.join(__dirname, '../../../data', 'setup.json'),
JSON.stringify(setup, null, 2),
'utf-8'
);
for (const [serviceName, serviceConfig] of Object.entries(newConfig.services)) {
const oldServiceConfig = oldConfig?.services?.[serviceName] || {};
await handleServices(serviceName, serviceConfig, oldServiceConfig);
}
}
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');
// 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;

View File

@@ -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,13 +31,21 @@ 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)) {
fs.mkdirSync(userDir, { recursive: true });
@@ -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);
@@ -58,6 +67,35 @@ router.post('/', (req, res) => {
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.',
filename: filename

View File

@@ -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 });

217
server.js
View File

@@ -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;
// Lecture du fichier de configuration
const loadSetup = async () => {
try {
setup = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'setup.json'), 'utf8'));
const setupPath = path.join(__dirname, 'data', 'setup.json');
const setupData = await fs.readFile(setupPath, 'utf8');
return JSON.parse(setupData);
} catch (err) {
console.error('Error reading setup.json:', err);
setup = {};
logger.error('Error reading setup.json:', err);
return {};
}
};
if (setup.discord !== undefined) {
require('./models/Passport-Discord.js');
}
// Configuration de l'application
const configureApp = async () => {
const setup = await loadSetup();
if (setup.ldap !== undefined) {
require('./models/Passport-ActiveDirectory.js');
}
// Configuration des stratégies d'authentification
if (setup.discord) require('./models/Passport-Discord.js');
if (setup.ldap) require('./models/Passport-ActiveDirectory.js');
app.use(express.static(path.join(__dirname, 'public')));
// Middleware de base
app.set('trust proxy', 1);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
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.' });
// 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 }));
function generateSecretKey() {
return crypto.randomBytes(64).toString('hex');
}
app.use(bodyParser.json());
app.use(session({
secret: generateSecretKey(),
secret: crypto.randomBytes(64).toString('hex'),
resave: false,
saveUninitialized: true,
cookie: { secure: false }
cookie: {
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000
}
}));
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(logRequestInfo);
app.use(routes);
app.use(logRequestInfo);
cron.schedule('00 03 * * *', async () => {
try {
const report = await SystemReport.generate();
if (report !== null) {
logger.info('System error report generated successfully');
}
} catch (err) {
ErrorLogger.error('Error generating report :', err);
app.use((req, res, next) => {
if (req.accepts('html')) {
res.status(404).render('unauthorized', { url: req.url });
} else {
// Pour les requêtes API ou autres
res.status(404).json({ error: 'Not Found' });
}
});
cron.schedule('0 * * * *', async () => {
try {
const fileInfoData = await fs.promises.readFile(path.join(__dirname, '/data/', 'file_info.json'), 'utf8');
const fileInfo = JSON.parse(fileInfoData);
app.use((err, req, res, next) => {
ErrorLogger.error('Unhandled error:', err);
const now = new Date();
res.status(500).json(response);
});
};
for (let index = fileInfo.length - 1; index >= 0; index--) {
const file = fileInfo[index];
const expiry = new Date(file.expiryDate);
if ((file.expiryDate && expiry <= now) || !(await fileExists(file.path))) {
fileInfo.splice(index, 1);
}
}
logger.info('Successfully checked file expirations and updated file_info.json');
} catch (err) {
ErrorLogger.error(`Failed to check file expirations: ${err}`);
process.removeAllListeners('warning');
process.on('warning', (warning) => {
if (warning.name === 'DeprecationWarning' && warning.message.includes('punycode')) {
return;
}
console.warn(warning.name, warning.message);
});
async function fileExists(filePath) {
// Configuration des tâches planifiées
const configureCronJobs = () => {
// 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.promises.access(filePath);
await fs.access(filePath);
return true;
} catch {
return false;
}
}
};
function getAllFiles(dirPath, arrayOfFiles) {
const files = fs.readdirSync(dirPath);
// Démarrage du serveur
const startServer = () => {
const server = app.listen(PORT, () => {
server.timeout = 300000;
console.clear()
arrayOfFiles = arrayOfFiles || [];
files.forEach(function(file) {
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles);
} else {
arrayOfFiles.push(path.join(dirPath, "/", file));
}
});
return arrayOfFiles;
}
const allFiles = getAllFiles(__dirname);
const SERVER = process.env.PORT || 5053;
app.listen(SERVER, () => {
SERVER.timeout = 300000
allFiles.forEach(file => {
console.log(`[ ${chalk.green('OK')} ] Loaded file: ${file}`);
});
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('☀️ 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}`);
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 };

131
services/BaseService.js Normal file
View File

@@ -0,0 +1,131 @@
const { logger } = require('../config/logs');
const fs = require('fs').promises;
const path = require('path');
class BaseService {
constructor(name, defaultConfig = {}) {
this.name = name;
this.defaultConfig = defaultConfig;
this.config = {};
this.isRunning = false;
this.status = 'stopped';
this.logger = logger;
this.setupPath = path.join(process.cwd(), 'data', 'setup.json');
}
async loadConfig() {
try {
const setupContent = await fs.readFile(this.setupPath, 'utf8');
const setup = JSON.parse(setupContent);
// Retrieve the service configuration from setup.json
const serviceConfig = setup[0]?.services?.[this.name] || {};
// Merge with the default configuration
this.config = {
...this.defaultConfig,
...serviceConfig
};
return this.config;
} catch (error) {
this.logger.error(`Error loading configuration for ${this.name}:`, error);
this.config = this.defaultConfig;
return this.config;
}
}
async start() {
if (this.isRunning) {
this.logger.warn(`Service ${this.name} is already running`);
return;
}
try {
// Load the configuration before starting
await this.loadConfig();
// Check if the service is enabled
if (this.config.enabled !== 'on') {
this.logger.info(`Service ${this.name} is not enabled in the configuration`);
return;
}
await this._startImplementation();
this.isRunning = true;
this.status = 'running';
this.logger.info(`Service ${this.name} started successfully`);
} catch (error) {
this.logger.error(`Error starting service ${this.name}:`, error);
this.status = 'error';
throw error;
}
}
async stop() {
if (!this.isRunning) {
this.logger.warn(`Service ${this.name} is not running`);
return;
}
try {
await this._stopImplementation();
this.isRunning = false;
this.status = 'stopped';
this.logger.info(`Service ${this.name} stopped successfully`);
} catch (error) {
this.logger.error(`Error stopping service ${this.name}:`, error);
this.status = 'error';
throw error;
}
}
getStatus() {
return {
name: this.name,
status: this.status,
isRunning: this.isRunning,
config: this.config
};
}
async updateConfig(newConfig) {
// Load the current configuration from setup.json
let setup;
try {
const setupContent = await fs.readFile(this.setupPath, 'utf8');
setup = JSON.parse(setupContent);
} catch (error) {
throw new Error(`Error reading setup.json: ${error.message}`);
}
// Update the service configuration
if (!setup[0].services) {
setup[0].services = {};
}
setup[0].services[this.name] = {
...this.config,
...newConfig
};
// Save to setup.json
try {
await fs.writeFile(this.setupPath, JSON.stringify(setup, null, 2));
this.config = setup[0].services[this.name];
this.logger.info(`Configuration for service ${this.name} updated:`, this.config);
} catch (error) {
throw new Error(`Error saving configuration: ${error.message}`);
}
}
// These methods must be implemented by subclasses
async _startImplementation() {
throw new Error('_startImplementation must be implemented by the subclass');
}
async _stopImplementation() {
throw new Error('_stopImplementation must be implemented by the subclass');
}
}
module.exports = BaseService;

View File

@@ -0,0 +1,141 @@
const BaseService = require('./BaseService');
const cron = require('node-cron');
const fs = require('fs').promises;
const path = require('path');
class FileCleanupService extends BaseService {
constructor(config = {}) {
super('fileCleanup', {
fileInfoPath: config.fileInfoPath || path.join(process.cwd(), 'data', 'file_info.json'),
cronSchedule: config.cronSchedule || '0 * * * *',
...config
});
this.job = null;
}
async _startImplementation() {
if (this.job) {
return;
}
// Valider le cron schedule
if (!cron.validate(this.config.cronSchedule)) {
throw new Error(`Schedule cron invalide: ${this.config.cronSchedule}`);
}
this.job = cron.schedule(this.config.cronSchedule, () => this.cleanup());
}
async _stopImplementation() {
if (this.job) {
this.job.stop();
this.job = null;
}
}
// Réutiliser vos méthodes existantes
async fileExists(filepath) {
try {
await fs.access(filepath);
return true;
} catch {
return false;
}
}
normalizePath(filepath) {
return path.normalize(filepath).replace(/\\/g, '/');
}
async readFileInfo() {
try {
const data = await fs.readFile(this.config.fileInfoPath, 'utf8');
return JSON.parse(data);
} catch (err) {
this.logger.error('Erreur lors de la lecture du fichier info:', err);
return [];
}
}
async saveFileInfo(fileInfo) {
try {
await fs.writeFile(
this.config.fileInfoPath,
JSON.stringify(fileInfo, null, 2),
'utf8'
);
return true;
} catch (err) {
this.logger.error('Erreur lors de la sauvegarde du fichier info:', err);
return false;
}
}
async processFile(file, now) {
const normalizedPath = this.normalizePath(file.path);
if (file.expiryDate && new Date(file.expiryDate) <= now) {
return { status: 'expired', file: { ...file, path: normalizedPath } };
}
if (!(await this.fileExists(normalizedPath))) {
return { status: 'missing', file: { ...file, path: normalizedPath } };
}
return { status: 'valid', file };
}
async cleanup() {
try {
const fileInfo = await this.readFileInfo();
const now = new Date();
const results = {
expired: [],
missing: [],
processed: 0,
remaining: 0
};
const validFiles = [];
await Promise.all(fileInfo.map(async (file) => {
const { status, file: processedFile } = await this.processFile(file, now);
if (status === 'expired') {
results.expired.push(processedFile);
results.processed++;
} else if (status === 'missing') {
results.missing.push(processedFile);
results.processed++;
} else {
validFiles.push(file);
}
}));
if (results.processed > 0) {
results.remaining = validFiles.length;
await this.saveFileInfo(validFiles);
this.logger.info('Nettoyage des fichiers terminé', {
expired: results.expired.length,
missing: results.missing.length,
totalProcessed: results.processed,
remainingFiles: results.remaining
});
}
return results;
} catch (err) {
this.logger.error('Erreur lors du nettoyage des fichiers:', {
error: err.message,
stack: err.stack
});
throw err;
}
}
async forceCleanup() {
return this.cleanup();
}
}
module.exports = new FileCleanupService();

181
services/reportService.js Normal file
View File

@@ -0,0 +1,181 @@
const BaseService = require('./BaseService');
const cron = require('node-cron');
const os = require('os');
const fs = require('fs');
const path = require('path');
const ip = require('ip');
const si = require('systeminformation');
const fetch = require('node-fetch');
const packageJson = require('../package.json');
class ReportManagerService extends BaseService {
constructor() {
// Configuration par défaut
const defaultConfig = {
enabled: 'off',
endpoint: 'https://cdn-apollon-p198-61m1.dinawo.fr/api/report/receive',
cronSchedule: '0 0 * * *' // Par défaut, une fois par jour à minuit
};
super('reportManager', defaultConfig);
this.job = null;
}
async _startImplementation() {
if (this.job) {
return;
}
// Valider le cron schedule
const schedule = this.config.cronSchedule || this.defaultConfig.cronSchedule;
if (!cron.validate(schedule)) {
throw new Error(`Schedule cron invalide: ${schedule}`);
}
this.job = cron.schedule(schedule, () => this.generateAndSendReport());
this.logger.info(`Service de rapport programmé avec le planning: ${schedule}`);
}
async _stopImplementation() {
if (this.job) {
this.job.stop();
this.job = null;
}
}
formatUptime(uptime) {
const days = Math.floor(uptime / (24 * 60 * 60));
uptime %= (24 * 60 * 60);
const hours = Math.floor(uptime / (60 * 60));
uptime %= (60 * 60);
const minutes = Math.floor(uptime / 60);
return `${days}d ${hours}h ${minutes}m`;
}
async getInternalErrors() {
const date = new Date();
date.setDate(date.getDate() - 1);
const previousDate = date.toISOString().split('T')[0];
const logFile = path.join(__dirname, '..', 'logs', `log-${previousDate}.log`);
try {
const logs = fs.readFileSync(logFile, 'utf-8');
return logs.split('\n').filter(line => /\[38;5;9mInternal-Error/.test(line));
} catch (err) {
this.logger.error('Erreur lors de la lecture des logs:', err);
return [];
}
}
async generateReport() {
try {
const internalErrors = await this.getInternalErrors();
if (internalErrors.length === 0) {
this.logger.info('Pas d\'erreurs internes dans les logs d\'hier. Pas de rapport généré.');
return null;
}
const loadavg = os.loadavg().map(load => (load / os.cpus().length) * 100);
const osInfo = {
type: os.type(),
platform: os.platform(),
arch: os.arch(),
release: os.release(),
uptime: this.formatUptime(os.uptime()),
loadavg: loadavg
};
const networkInterfaces = os.networkInterfaces();
const diskUsage = await si.fsSize();
const cpuTemperature = await si.cpuTemperature();
const userInfo = os.userInfo();
const systemInfo = {
memoryInfo: ((os.totalmem() - os.freemem()) / os.totalmem() * 100).toFixed(2),
cpuInfo: (os.cpus().length / os.cpus().length * 100).toFixed(2),
diskInfo: diskUsage.map(disk => ({
fs: disk.fs,
type: disk.type,
size: disk.size,
used: disk.used,
available: disk.available,
use: disk.use
})),
ipAddress: ip.address(),
cdnVersion: packageJson.version,
osInfo: osInfo,
userInfo: {
username: userInfo.username,
homedir: userInfo.homedir,
shell: userInfo.shell
},
errors: internalErrors,
networkInterfaces: networkInterfaces,
serverUptime: os.uptime(),
systemLoad: loadavg,
cpuTemperature: cpuTemperature
};
const reportDate = new Date();
reportDate.setDate(reportDate.getDate() - 1);
const reportFileName = `report_${reportDate.toISOString().split('T')[0]}_${ip.address()}.json`;
// Sauvegarder le rapport localement
const reportDir = path.join(__dirname, '..', 'report');
if (!fs.existsSync(reportDir)) {
fs.mkdirSync(reportDir, { recursive: true });
}
fs.writeFileSync(
path.join(reportDir, reportFileName),
JSON.stringify(systemInfo, null, 2)
);
return systemInfo;
} catch (error) {
this.logger.error('Erreur lors de la génération du rapport:', error);
throw error;
}
}
async generateAndSendReport() {
try {
const report = await this.generateReport();
if (!report) {
return;
}
this.logger.info("Envoi du rapport...");
const response = await fetch(this.config.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(report)
});
if (!response.ok) {
throw new Error(`Erreur HTTP! statut: ${response.status}`);
}
const responseData = await response.json();
this.logger.info('Rapport envoyé avec succès. Réponse:', responseData);
return responseData;
} catch (error) {
this.logger.error('Échec de l\'envoi du rapport:', error);
throw error;
}
}
// Méthode pour forcer l'envoi d'un rapport immédiatement
async forceSendReport() {
return this.generateAndSendReport();
}
}
module.exports = new ReportManagerService();

View File

@@ -69,14 +69,25 @@
</button>
<div class="dropdown-menu dropdown-menu-right" id="accountDropdownMenu">
<a class="dropdown-item" href="/dpanel/dashboard/profil">
<i class="fas fa-user"></i> Mon profil
<span style="display: inline-block; width: 20px; text-align: center;">
<i class="fas fa-user"></i>
</span>
Mon profil
</a>
<% if (user.role === 'admin') { %>
<a class="dropdown-item" href="/dpanel/dashboard/admin">
<i class="fas fa-user-shield"></i> Administration du site
<span style="display: inline-block; width: 20px; text-align: center;">
<i class="fas fa-user-shield"></i>
</span>
Administration du site
</a>
<% } %>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/auth/logout">
<i class="fas fa-sign-out-alt"></i> Déconnexion
<span style="display: inline-block; width: 20px; text-align: center;">
<i class="fas fa-sign-out-alt"></i>
</span>
Déconnexion
</a>
</div>
</li>
@@ -152,7 +163,7 @@
<footer class="footer mt-auto py-3 bg-light">
<div class="container">
<span class="text-muted">Version: 1.0.0-beta.17 | &copy; 2024 Myaxrin Labs</span>
<span class="text-muted">Version: <span id="version-number">...</span> | &copy; 2024 Myaxrin Labs</span>
<a href="#" class="float-right" onclick="displayMetadata()">Metadata</a>
</div>
</footer>

View File

@@ -191,7 +191,7 @@
<div class="container">
<footer class="py-3 my-4">
<ul class="nav justify-content-center border-bottom pb-3 mb-3">
<li class="nav-item"><a class="nav-link px-2 text-muted">Version: 1.0.0-beta.17</a></li>
<li class="nav-item"><a class="nav-link px-2 text-muted">Version: <span id="version-number">...</span></a></li>
</ul>
<p class="text-center text-muted">&copy; 2024 Myaxrin Labs</p>
</footer>

View File

@@ -4,12 +4,20 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Parameter Admin</title>
<link rel="icon" href="/public/assets/homelab_logo.png" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link rel="stylesheet" href="/public/css/paramadminsettingsetup.styles.css">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
<style>
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;
background-image: url('<%= user.wallpaper %>');
background-size: cover;
background-position: center;
@@ -17,448 +25,376 @@
background-attachment: fixed;
margin: 0;
min-height: 100vh;
overflow-y: auto;
}
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
:root {
--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 {
--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%;
}
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;
}
.container {
max-width: 1830px;
margin: 0 auto;
padding: 2rem;
}
.form-container {
background-color: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: var(--radius);
padding: 2rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-control {
width: 100%;
padding: 0.5rem;
border: 1px solid hsl(var(--border));
border-radius: var(--radius);
background-color: hsl(var(--background));
color: hsl(var(--foreground));
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius);
font-weight: 500;
transition: all 0.3s ease;
cursor: pointer;
}
.btn-primary {
background-color: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
.btn-primary:hover {
opacity: 0.9;
}
.btn-secondary {
background-color: hsl(var(--secondary));
color: hsl(var(--secondary-foreground));
}
.btn-secondary:hover {
opacity: 0.9;
}
#themeSwitcher {
position: fixed;
top: 1rem;
right: 1rem;
}
.animate {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.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: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: hsl(var(--background));
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: hsl(var(--primary));
}
input:checked + .slider:before {
transform: translateX(26px);
}
.fade-out {
animation: fadeOut 0.3s ease-out forwards;
}
@keyframes fadeOut {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-10px); }
}
.ip-error {
border-color: #ef4444 !important;
display: flex;
flex-direction: column;
}
</style>
</head>
<body class="animate">
<div id="app" class="min-h-screen flex items-center justify-center">
<div class="container mt-8">
<h1 class="text-3xl font-semibold mb-6 text-center animate">Gestion de la configuration</h1>
<div class="form-container">
<form id="setupForm" action="/api/dpanel/dashboard/admin/update-setup" method="POST" class="mb-4 animate">
<h2 class="text-2xl font-semibold mb-4">Paramètres LDAP</h2>
<div class="form-group animate flex items-center justify-between">
<label for="ldapEnabled" class="block mb-2">Activer LDAP :</label>
<label class="switch">
<input type="checkbox" id="ldapEnabled" name="ldap[enabled]" <%= setup.ldap && setup.ldap.enabled === 'on' ? 'checked' : '' %> onchange="toggleForm('ldapForm', this)">
<span class="slider"></span>
</label>
</div>
<div id="ldapForm" style="display: <%= setup.ldap && setup.ldap.enabled === 'on' ? 'block' : 'none' %>">
<div class="form-group animate">
<label for="ldapUrl" class="block mb-2">URL :</label>
<input type="text" id="ldapUrl" name="ldap[url]" class="form-control" value="<%= setup.ldap ? setup.ldap.url : '' %>">
</div>
<div class="form-group animate">
<label for="ldapBaseDN" class="block mb-2">Base DN :</label>
<input type="text" id="ldapBaseDN" name="ldap[baseDN]" class="form-control" value="<%= setup.ldap ? setup.ldap.baseDN : '' %>">
</div>
<div class="form-group animate">
<label for="ldapUsername" class="block mb-2">Nom d'utilisateur :</label>
<input type="text" id="ldapUsername" name="ldap[username]" class="form-control" value="<%= setup.ldap ? setup.ldap.username : '' %>">
</div>
<div class="form-group animate">
<label for="ldapPassword" class="block mb-2">Mot de passe :</label>
<input type="password" id="ldapPassword" name="ldap[password]" class="form-control" value="<%= setup.ldap ? setup.ldap.password : '' %>">
</div>
<div id="app" class="min-h-screen">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<div class="mb-8">
<h1 class="text-3xl font-semibold mb-2">Gestion de la configuration</h1>
<p class="text-gray-500">Gérez les paramètres et la configuration de votre application</p>
</div>
<h2 class="text-2xl font-semibold mb-4 mt-8">Paramètres Discord</h2>
<div class="form-group animate flex items-center justify-between">
<label for="discordEnabled" class="block mb-2">Activer Discord :</label>
<label class="switch">
<input type="checkbox" id="discordEnabled" name="discord[enabled]" <%= setup.discord && setup.discord.enabled === 'on' ? 'checked' : '' %> onchange="toggleForm('discordForm', this)">
<span class="slider"></span>
</label>
</div>
<div id="discordForm" style="<%= `display: ${setup.discord?.enabled === 'on' ? 'block' : 'none'}` %>">
<div class="form-group animate">
<label for="discordClientID" class="block mb-2">ID Client :</label>
<input type="text" id="discordClientID" name="discord[clientID]" class="form-control" value="<%= setup.discord ? setup.discord.clientID : '' %>">
</div>
<div class="form-group animate">
<label for="discordClientSecret" class="block mb-2">Secret Client :</label>
<input type="password" id="discordClientSecret" name="discord[clientSecret]" class="form-control" value="<%= setup.discord ? setup.discord.clientSecret : '' %>">
</div>
<div class="form-group animate">
<label for="discordIdentifyURL" class="block mb-2">URL d'identification :</label>
<input type="text" id="discordIdentifyURL" name="discord[identifyURL]" class="form-control" value="<%= setup.discord ? setup.discord.identifyURL : '' %>">
</div>
<div class="form-group animate">
<label for="discordAuthorizedIDs" class="block mb-2">IDs Autorisés :</label>
<input type="text" id="discordAuthorizedIDs" name="discord[authorizedIDs]" class="form-control" value="<%= setup.discord ? setup.discord.authorizedIDs : '' %>">
</div>
<!-- Tabs Navigation -->
<div class="tabs">
<button class="tab active" data-tab="general">
<i class="fas fa-cog"></i>
<span>Général</span>
</button>
<button class="tab" data-tab="authentication">
<i class="fas fa-lock"></i>
<span>Authentification</span>
</button>
<button class="tab" data-tab="services">
<i class="fas fa-server"></i>
<span>Services</span>
</button>
<button class="tab" data-tab="security">
<i class="fas fa-shield-alt"></i>
<span>Sécurité</span>
</button>
<button class="tab" data-tab="logs">
<i class="fas fa-list"></i>
<span>Logs</span>
</button>
</div>
<h2 class="text-2xl font-semibold mb-4 mt-8">Autres Paramètres</h2>
<div class="form-group animate">
<form id="setupForm" action="/api/dpanel/dashboard/admin/update-setup" method="POST">
<!-- General Tab -->
<div class="tab-content active" data-tab-content="general">
<div class="settings-card">
<div class="settings-card-header">
<i class="fas fa-globe text-xl"></i>
<h2 class="settings-card-title">Configuration du domaine</h2>
</div>
<div class="form-group">
<label for="domain" class="block mb-2">Domaine :</label>
<input type="text" id="domain" name="domain" class="form-control" value="<%= setup.domain %>">
</div>
<div class="form-group animate">
<div class="form-group">
<label for="webhooksDiscord" class="block mb-2">Webhooks Discord :</label>
<input type="text" id="webhooksDiscord" name="webhooks_discord" class="form-control" value="<%= setup.webhooks_discord %>">
</div>
</div>
</div>
<div class="form-group animate">
<label class="block mb-2">IPs Autorisées pour les Webhooks :</label>
<div class="space-y-2">
<!-- Authentication Tab -->
<div class="tab-content" data-tab-content="authentication">
<!-- LDAP Section -->
<div class="settings-card">
<div class="settings-card-header">
<i class="fas fa-database text-xl"></i>
<h2 class="settings-card-title">Configuration LDAP</h2>
</div>
<div class="form-group flex items-center justify-between">
<label for="ldapEnabled">Activer LDAP :</label>
<label class="switch">
<input type="checkbox" id="ldapEnabled" name="ldap[enabled]"
<%= setup.ldap?.enabled === 'on' ? 'checked' : '' %>
onchange="toggleSection('ldapForm', this)">
<span class="slider"></span>
</label>
</div>
<div id="ldapForm" class="nested-settings" style="display: <%= setup.ldap?.enabled === 'on' ? 'block' : 'none' %>">
<div class="form-group">
<label for="ldapUrl">URL :</label>
<input type="text" id="ldapUrl" name="ldap[url]" class="form-control" value="<%= setup.ldap?.url %>">
</div>
<div class="form-group">
<label for="ldapBaseDN">Base DN :</label>
<input type="text" id="ldapBaseDN" name="ldap[baseDN]" class="form-control" value="<%= setup.ldap?.baseDN %>">
</div>
<div class="form-group">
<label for="ldapUsername">Nom d'utilisateur :</label>
<input type="text" id="ldapUsername" name="ldap[username]" class="form-control" value="<%= setup.ldap?.username %>">
</div>
<div class="form-group">
<label for="ldapPassword">Mot de passe :</label>
<input type="password" id="ldapPassword" name="ldap[password]" class="form-control" value="<%= setup.ldap?.password %>">
</div>
</div>
</div>
<!-- Discord Section -->
<div class="settings-card">
<div class="settings-card-header">
<i class="fab fa-discord text-xl"></i>
<h2 class="settings-card-title">Configuration Discord</h2>
</div>
<div class="form-group flex items-center justify-between">
<label for="discordEnabled">Activer Discord :</label>
<label class="switch">
<input type="checkbox" id="discordEnabled" name="discord[enabled]"
<%= setup.discord?.enabled === 'on' ? 'checked' : '' %>
onchange="toggleSection('discordForm', this)">
<span class="slider"></span>
</label>
</div>
<div id="discordForm" class="nested-settings" style="display: <%= setup.discord?.enabled === 'on' ? 'block' : 'none' %>">
<div class="form-group">
<label for="discordClientID">ID Client :</label>
<input type="text" id="discordClientID" name="discord[clientID]" class="form-control" value="<%= setup.discord?.clientID %>">
</div>
<div class="form-group">
<label for="discordClientSecret">Secret Client :</label>
<input type="password" id="discordClientSecret" name="discord[clientSecret]" class="form-control" value="<%= setup.discord?.clientSecret %>">
</div>
<div class="form-group">
<label for="discordAuthorizedIDs">IDs Autorisés :</label>
<input type="text" id="discordAuthorizedIDs" name="discord[authorizedIDs]" class="form-control" value="<%= setup.discord?.authorizedIDs %>">
</div>
</div>
</div>
</div>
<!-- Services Tab -->
<div class="tab-content" data-tab-content="services">
<!-- Services Configuration -->
<div class="settings-card">
<div class="settings-card-header">
<i class="fas fa-broom text-xl"></i>
<h2 class="settings-card-title">Service de nettoyage des fichiers</h2>
</div>
<div class="form-group flex items-center justify-between">
<div>
<label>Service de nettoyage :</label>
<p class="text-sm text-gray-500">Nettoie automatiquement les fichiers temporaires</p>
</div>
<label class="switch">
<input type="checkbox"
name="services[fileCleanup][enabled]"
<%= setup.services?.fileCleanup?.enabled === 'on' ? 'checked' : '' %>
onchange="toggleSection('fileCleanupConfig', this)">
<span class="slider"></span>
</label>
</div>
<div id="fileCleanupConfig" class="nested-settings" style="display: <%= setup.services?.fileCleanup?.enabled === 'on' ? 'block' : 'none' %>">
<div class="form-group">
<label>Planning (Cron) :</label>
<input type="text"
name="services[fileCleanup][schedule]"
class="form-control"
value="<%= setup.services?.fileCleanup?.schedule || '0 * * * *' %>"
placeholder="0 * * * *">
</div>
</div>
</div>
<!-- Report Manager -->
<div class="settings-card">
<div class="settings-card-header">
<i class="fas fa-chart-line text-xl"></i>
<h2 class="settings-card-title">Gestionnaire de rapports</h2>
</div>
<div class="form-group flex items-center justify-between">
<div>
<label>Service de rapports :</label>
<p class="text-sm text-gray-500">Génère des rapports périodiques</p>
</div>
<label class="switch">
<input type="checkbox"
name="services[reportManager][enabled]"
<%= setup.services?.reportManager?.enabled === 'on' ? 'checked' : '' %>
onchange="toggleSection('reportManagerConfig', this)">
<span class="slider"></span>
</label>
</div>
<div id="reportManagerConfig" class="nested-settings" style="display: <%= setup.services?.reportManager?.enabled === 'on' ? 'block' : 'none' %>">
<div class="form-group">
<label>URL Endpoint :</label>
<input type="text"
name="services[reportManager][endpoint]"
class="form-control"
value="<%= setup.services?.reportManager?.endpoint %>">
</div>
<div class="form-group">
<label>Planning (Cron) :</label>
<input type="text"
name="services[reportManager][schedule]"
class="form-control"
value="<%= setup.services?.reportManager?.schedule || '0 0 * * *' %>">
</div>
</div>
</div>
</div>
<!-- Security Tab -->
<div class="tab-content" data-tab-content="security">
<div class="settings-card">
<div class="settings-card-header">
<i class="fas fa-shield-alt text-xl"></i>
<h2 class="settings-card-title">IPs Autorisées</h2>
</div>
<div class="form-group">
<div id="ipList" class="space-y-2">
<% if (setup.allowedIps && setup.allowedIps.length > 0) { %>
<% setup.allowedIps.forEach((ip, index) => { %>
<div class="flex items-center space-x-2">
<% if (setup.allowedIps?.length > 0) { %>
<% setup.allowedIps.forEach(ip => { %>
<div class="ip-entry flex items-center gap-2">
<input type="text" name="allowedIps[]" class="form-control flex-1" value="<%= ip %>">
<button type="button" class="btn btn-secondary p-2" onclick="removeIpField(this)">
<button type="button" class="btn btn-icon" onclick="removeIp(this)">
<i class="fas fa-trash"></i>
</button>
</div>
<% }) %>
<% } else { %>
<div class="flex items-center space-x-2">
<input type="text" name="allowedIps[]" class="form-control flex-1"
placeholder="IPv4/IPv6 (ex: 192.168.1.1 ou 2001:db8::1 ou CIDR)">
<button type="button" class="btn btn-secondary p-2" onclick="removeIpField(this)">
<div class="ip-entry flex items-center gap-2">
<input type="text" name="allowedIps[]" class="form-control flex-1" placeholder="IPv4/IPv6 ou CIDR">
<button type="button" class="btn btn-icon" onclick="removeIp(this)">
<i class="fas fa-trash"></i>
</button>
</div>
<% } %>
</div>
<button type="button" class="btn btn-secondary w-full py-2" onclick="addIpField()">
<i class="fas fa-plus mr-2"></i>
Ajouter une IP
<button type="button" class="btn btn-secondary w-full mt-4" onclick="addIp()">
<i class="fas fa-plus mr-2"></i>Ajouter une IP
</button>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary w-full py-2 mt-4">
<i class="fas fa-save mr-2"></i>
Mettre à jour
<!-- Logs Tab -->
<div class="tab-content" data-tab-content="logs">
<div class="settings-card">
<div class="settings-card-header">
<i class="fas fa-list text-xl"></i>
<h2 class="settings-card-title">Configuration des Logs</h2>
</div>
<div class="form-group flex items-center justify-between">
<label for="logsEnabled">Activer les Logs :</label>
<label class="switch">
<input type="checkbox"
id="logsEnabled"
name="logs[enabled]"
<%= setup.logs?.enabled === 'on' ? 'checked' : '' %>
onchange="toggleSection('logsConfig', this)">
<span class="slider"></span>
</label>
</div>
<div id="logsConfig" class="nested-settings" style="display: <%= setup.logs?.enabled === 'on' ? 'block' : 'none' %>">
<div class="form-group">
<label>Niveaux de Logs :</label>
<div class="flex flex-wrap gap-2 mt-2">
<% ['Debug', 'Info', 'Warn', 'Error'].forEach(level => { %>
<button type="button"
onclick="toggleLogLevel(this)"
class="log-level-btn <%= setup.logs?.levels?.includes(level.toLowerCase()) ? 'active' : '' %>"
data-level="<%= level.toLowerCase() %>">
<%= level %>
<input type="checkbox"
name="logs[levels][]"
value="<%= level.toLowerCase() %>"
class="hidden"
<%= setup.logs?.levels?.includes(level.toLowerCase()) ? 'checked' : '' %>>
</button>
<% }) %>
</div>
</div>
<!-- Chemins exclus -->
<div class="form-group mt-4">
<label>Chemins à Exclure :</label>
<div class="flex gap-2 mt-2">
<div class="relative flex-1">
<input type="text"
id="excludePathInput"
class="form-control"
placeholder="/api/, /auth/, /public/, etc">
<button type="button"
onclick="addPath('exclude')"
class="absolute right-2 top-1/2 transform -translate-y-1/2">
<i class="fas fa-plus"></i>
</button>
</div>
<button type="button"
onclick="showCommonPaths('exclude')"
class="btn btn-secondary">
<i class="fas fa-bars"></i>
<span>Communs</span>
</button>
</div>
<div id="excludePathsList" class="path-list mt-2">
<% if (setup.logs?.excludePaths?.length > 0) { %>
<% setup.logs.excludePaths.forEach(path => { %>
<div class="path-entry">
<span><%= path %></span>
<button type="button" onclick="removePath(this)">
<i class="fas fa-times"></i>
</button>
<input type="hidden" name="logs[excludePaths][]" value="<%= path %>">
</div>
<% }) %>
<% } %>
</div>
</div>
<!-- Chemins inclus -->
<div class="form-group mt-4">
<label>Chemins à Inclure Uniquement :</label>
<div class="flex gap-2 mt-2">
<div class="relative flex-1">
<input type="text"
id="includePathInput"
class="form-control"
placeholder="/api/, /auth/, /public/, etc">
<button type="button"
onclick="addPath('include')"
class="absolute right-2 top-1/2 transform -translate-y-1/2">
<i class="fas fa-plus"></i>
</button>
</div>
<button type="button"
onclick="showCommonPaths('include')"
class="btn btn-secondary">
<i class="fas fa-bars"></i>
<span>Communs</span>
</button>
</div>
<div id="includePathsList" class="path-list mt-2">
<% if (setup.logs?.includeOnly?.length > 0) { %>
<% setup.logs.includeOnly.forEach(path => { %>
<div class="path-entry">
<span><%= path %></span>
<button type="button" onclick="removePath(this)">
<i class="fas fa-times"></i>
</button>
<input type="hidden" name="logs[includeOnly][]" value="<%= path %>">
</div>
<% }) %>
<% } %>
</div>
</div>
</div>
</div>
</div>
</form>
<a href="/dpanel/dashboard/admin/" class="btn btn-secondary w-full py-2 mt-4 text-center">
<!-- Fixed Bottom Bar -->
<div class="fixed-bottom-bar">
<div class="container mx-auto px-4 flex justify-between items-center">
<a href="/dpanel/dashboard/admin/" class="btn btn-secondary">
<i class="fas fa-arrow-left mr-2"></i>
Retour au tableau de bord admin
Retour
</a>
<button type="submit" form="setupForm" class="btn btn-primary">
<i class="fas fa-save mr-2"></i>
Enregistrer les modifications
</button>
</div>
</div>
</div>
</div>
<button id="themeSwitcher" class="btn btn-secondary p-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
</svg>
<!-- Theme Switcher -->
<button id="themeSwitcher" class="theme-switcher">
<i class="fas fa-sun"></i>
</button>
<script>
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 = `
<input type="text" name="allowedIps[]" class="form-control flex-1"
placeholder="ex: 10.20.0.34 ou 10.20.0.0/24">
<button type="button" class="btn btn-secondary p-2" onclick="removeIpField(this)">
<i class="fas fa-trash"></i>
</button>
`;
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;
// Gestion CIDR
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;
}
// IP simple
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) {
// Regex pour IPv6 standard et compressé
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);
}
// Modification de la validation du formulaire
document.getElementById('setupForm').addEventListener('submit', function(e) {
e.preventDefault();
const ipInputs = document.querySelectorAll('input[name="allowedIps[]"]');
let hasError = false;
// Réinitialiser les styles d'erreur
ipInputs.forEach(input => {
input.classList.remove('ip-error');
});
// Valider chaque IP
ipInputs.forEach(input => {
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',
html: `
Veuillez vérifier le format des IPs saisies.<br><br>
Formats acceptés :<br>
- IPv4 (ex: 192.168.1.1)<br>
- IPv4 CIDR (ex: 192.168.1.0/24)<br>
- IPv6 (ex: 2001:db8::1)<br>
- IPv6 CIDR (ex: 2001:db8::/32)<br>
- IPv6 locale (ex: fe80::1)<br>
- IPv4-mapped IPv6 (ex: ::ffff:192.168.1.1)
`
});
return;
}
// Soumettre le formulaire si tout est valide
this.submit();
});
</script>
<script src="/public/js/paramadminsettingsetup.script.js"></script>
</body>
</html>

View File

@@ -152,9 +152,8 @@
</div>
</main>
<!-- Footer -->
<footer class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 text-center text-gray-500 text-sm opacity-0 animate-fade-in-up">
<p>Version: 1.0.0-beta.17 | © 2024 Myaxrin Labs</p>
<p>Version: <span id="version-number">...</span> | © 2024 Myaxrin Labs</p>
</footer>
<script>
@@ -163,6 +162,17 @@
elements.forEach(element => {
element.classList.add('animate-fade-in-up-visible');
});
// 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';
});
</script>
</body>
</html>