This commit is contained in:
@@ -10,14 +10,14 @@ steps:
|
|||||||
from_secret: git_password
|
from_secret: git_password
|
||||||
commands:
|
commands:
|
||||||
- npm install
|
- 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
|
- name: build-docker-image
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
repo: swiftlogiclabs/cdn-app-insider
|
repo: swiftlogiclabs/cdn-app-insider
|
||||||
tags:
|
tags_file: .tags
|
||||||
- latest
|
|
||||||
- v${VERSION}
|
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
|||||||
299
config/logs.js
299
config/logs.js
@@ -1,147 +1,194 @@
|
|||||||
const { format } = require('winston');
|
const { format } = require('winston');
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
const DailyRotateFile = require('winston-daily-rotate-file');
|
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) => {
|
const logPrefix = (label) => {
|
||||||
if (label === 'server') {
|
const prefixes = {
|
||||||
return '[🖥️ \x1b[32mServer\x1b[0m] ';
|
'server': '[🖥️ \x1b[32mServer\x1b[0m]',
|
||||||
} else if (label === 'client') {
|
'client': '[🌐 \x1b[34mClient\x1b[0m]',
|
||||||
return '[🌐 \x1b[34mClient\x1b[0m] ';
|
'Internal-Server-Error': '[❗️ \x1b[38;5;9mInternal-Error\x1b[0m]',
|
||||||
} else if (label === 'Internal-Server-Error') {
|
'Authentification': '[🔒 \x1b[33mAuthentification\x1b[0m]',
|
||||||
return '[❗️ \x1b[38;5;9mInternal-Error\x1b[0m] ';
|
'Suspicious Request': '[⚠️ \x1b[38;5;208mSuspicious Request\x1b[0m]',
|
||||||
} else if (label === 'Authentification') {
|
'API Request': '[⚙️ \x1b[38;5;9mAPI Request\x1b[0m]',
|
||||||
return '[🔒 \x1b[33mAuthentification\x1b[0m] ';
|
'File System': '[📁 \x1b[36mFile System\x1b[0m]',
|
||||||
} else if (label === 'Suspicious Request') {
|
'Database': '[🗄️ \x1b[35mDatabase\x1b[0m]'
|
||||||
return '[⚠️ \x1b[38;5;208mSuspicious Request\x1b[0m] ';
|
};
|
||||||
}else if (label === 'API Request') {
|
return prefixes[label] || '';
|
||||||
return '[⚙️ \x1b[38;5;9mAPI Request\x1b[0m] ';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Configuration du transport de fichiers rotatifs
|
||||||
const createDailyRotateFileTransport = () => {
|
const createDailyRotateFileTransport = () => {
|
||||||
return new DailyRotateFile({
|
return new DailyRotateFile({
|
||||||
filename: 'logs/log-%DATE%.log',
|
filename: 'logs/log-%DATE%.log',
|
||||||
datePattern: 'YYYY-MM-DD',
|
datePattern: 'YYYY-MM-DD',
|
||||||
zippedArchive: false,
|
zippedArchive: true,
|
||||||
maxSize: '20m',
|
maxSize: '20m',
|
||||||
maxFiles: '14d'
|
maxFiles: '14d',
|
||||||
});
|
format: format.combine(
|
||||||
|
format.timestamp(),
|
||||||
|
format.json()
|
||||||
|
)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = winston.createLogger({
|
// Configuration du format de base pour tous les loggers
|
||||||
format: format.combine(
|
const createLoggerFormat = (label) => {
|
||||||
format.label({ label: 'server' }),
|
return format.combine(
|
||||||
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
format.label({ label }),
|
||||||
format.printf(info => {
|
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
const { timestamp, level, label, message } = info;
|
format.printf(info => {
|
||||||
const prefix = logPrefix(label);
|
const { timestamp, label, message } = info;
|
||||||
return `${prefix}[\x1b[36m${timestamp}\x1b[0m] ${message}`;
|
const prefix = logPrefix(label);
|
||||||
})
|
return `${prefix} [\x1b[36m${timestamp}\x1b[0m] ${message}`;
|
||||||
),
|
})
|
||||||
transports: [
|
);
|
||||||
new winston.transports.Console(),
|
};
|
||||||
createDailyRotateFileTransport()
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
const ErrorLogger = winston.createLogger({
|
// Création des différents loggers
|
||||||
format: format.combine(
|
const createLogger = (label) => {
|
||||||
format.label({ label: 'Internal-Server-Error' }),
|
return winston.createLogger({
|
||||||
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
format: createLoggerFormat(label),
|
||||||
format.printf(info => {
|
transports: [
|
||||||
const { timestamp, level, label, message } = info;
|
new winston.transports.Console(),
|
||||||
const prefix = logPrefix(label);
|
createDailyRotateFileTransport()
|
||||||
return `${prefix}[\x1b[36m${timestamp}\x1b[0m] ${message}`;
|
]
|
||||||
})
|
});
|
||||||
),
|
};
|
||||||
transports: [
|
|
||||||
new winston.transports.Console(),
|
|
||||||
createDailyRotateFileTransport()
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
const clientLogger = winston.createLogger({
|
// Instanciation des loggers
|
||||||
format: format.combine(
|
const logger = createLogger('server');
|
||||||
format.label({ label: 'client' }),
|
const clientLogger = createLogger('client');
|
||||||
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
const ErrorLogger = createLogger('Internal-Server-Error');
|
||||||
format.printf(info => {
|
const authLogger = createLogger('Authentification');
|
||||||
const { timestamp, level, label, message } = info;
|
const suspiciousLogger = createLogger('Suspicious Request');
|
||||||
const prefix = logPrefix(label);
|
const apiLogger = createLogger('API Request');
|
||||||
return `${prefix}[\x1b[36m${timestamp}\x1b[0m] ${message}`;
|
const fileSystemLogger = createLogger('File System');
|
||||||
})
|
const databaseLogger = createLogger('Database');
|
||||||
),
|
|
||||||
transports: [
|
|
||||||
new winston.transports.Console(),
|
|
||||||
createDailyRotateFileTransport()
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
const authLogger = winston.createLogger({
|
// Fonction utilitaire pour vérifier les chemins
|
||||||
format: format.combine(
|
const checkPath = (url, paths) => {
|
||||||
format.label({ label: 'Authentification' }),
|
return paths && paths.length > 0 && paths.some(p => url.startsWith(p.trim()));
|
||||||
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()
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Middleware principal de logging
|
||||||
const logRequestInfo = (req, res, next) => {
|
const logRequestInfo = (req, res, next) => {
|
||||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
try {
|
||||||
const userAgent = req.headers['user-agent'];
|
// Lire la configuration
|
||||||
req.log = clientLogger;
|
const setup = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'data', 'setup.json'), 'utf-8'))[0];
|
||||||
req.log.info(`[${ip}] - ${userAgent} - ${req.method} ${req.url}`);
|
const logsConfig = setup.logs || { enabled: 'off', level: 'info' };
|
||||||
next();
|
|
||||||
};
|
|
||||||
|
|
||||||
const apiLogger = winston.createLogger({
|
// Vérifier si les logs sont activés
|
||||||
format: format.combine(
|
if (logsConfig.enabled !== 'on') {
|
||||||
format.label({ label: 'API Request' }),
|
return next();
|
||||||
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 ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||||
|
const userAgent = req.headers['user-agent'];
|
||||||
|
const url = req.originalUrl || req.url;
|
||||||
|
|
||||||
|
// Vérifier d'abord includeOnly
|
||||||
|
if (logsConfig.includeOnly && logsConfig.includeOnly.length > 0) {
|
||||||
|
const isIncluded = logsConfig.includeOnly.some(p => url.startsWith(p.trim()));
|
||||||
|
if (!isIncluded) {
|
||||||
|
return next(); // Ne pas logger si le chemin n'est pas dans includeOnly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensuite vérifier excludePaths
|
||||||
|
if (logsConfig.excludePaths && logsConfig.excludePaths.length > 0) {
|
||||||
|
const isExcluded = logsConfig.excludePaths.some(p => url.startsWith(p.trim()));
|
||||||
|
if (isExcluded) {
|
||||||
|
return next(); // Ne pas logger si le chemin est dans excludePaths
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sélectionner le logger approprié et le niveau
|
||||||
|
let selectedLogger = clientLogger;
|
||||||
|
let logLevel = logsConfig.level || 'info';
|
||||||
|
|
||||||
|
if (url.startsWith('/api')) {
|
||||||
|
selectedLogger = apiLogger;
|
||||||
|
} else if (url.startsWith('/auth')) {
|
||||||
|
selectedLogger = authLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ne logger que si le niveau est activé
|
||||||
|
if (!logsConfig.levels || logsConfig.levels.includes(logLevel)) {
|
||||||
|
const logMessage = `[${ip}] - ${userAgent} - ${req.method} ${url}`;
|
||||||
|
selectedLogger[logLevel](logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in logRequestInfo:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware spécifique pour les requêtes API
|
||||||
const logApiRequest = (req, res, next) => {
|
const logApiRequest = (req, res, next) => {
|
||||||
const start = Date.now();
|
if (!req.originalUrl.startsWith('/api')) {
|
||||||
res.on('finish', () => {
|
return next();
|
||||||
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`);
|
const start = Date.now();
|
||||||
});
|
res.on('finish', () => {
|
||||||
next();
|
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
|
||||||
|
};
|
||||||
@@ -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
425
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@cdn-app/insider-myaxrin-labs-dinawo",
|
"name": "@cdn-app/insider-myaxrin-labs-dinawo",
|
||||||
"version": "1.0.0-beta.17",
|
"version": "1.1.0-beta.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@cdn-app/insider-myaxrin-labs-dinawo",
|
"name": "@cdn-app/insider-myaxrin-labs-dinawo",
|
||||||
"version": "1.0.0-beta.17",
|
"version": "1.1.0-beta.1",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/express": "^0.5.1",
|
"@auth/express": "^0.5.1",
|
||||||
@@ -256,9 +256,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/set-array": "^1.2.1",
|
"@jridgewell/set-array": "^1.2.1",
|
||||||
@@ -454,9 +454,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.10.1",
|
"version": "22.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
|
||||||
"integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==",
|
"integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.20.0"
|
||||||
@@ -493,6 +493,15 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/activedirectory": {
|
||||||
"version": "0.7.2",
|
"version": "0.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/activedirectory/-/activedirectory-0.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/activedirectory/-/activedirectory-0.7.2.tgz",
|
||||||
@@ -827,9 +836,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.7.8",
|
"version": "1.7.9",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||||
"integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
|
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
@@ -1111,16 +1120,44 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-bind": {
|
"node_modules/call-bind": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.0",
|
||||||
"es-define-property": "^1.0.0",
|
"es-define-property": "^1.0.0",
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"function-bind": "^1.1.2",
|
|
||||||
"get-intrinsic": "^1.2.4",
|
"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": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -1346,15 +1383,6 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -1515,9 +1543,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/daisyui": {
|
"node_modules/daisyui": {
|
||||||
"version": "4.12.14",
|
"version": "4.12.22",
|
||||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.14.tgz",
|
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.22.tgz",
|
||||||
"integrity": "sha512-hA27cdBasdwd4/iEjn+aidoCrRroDuo3G5W9NDKaVCJI437Mm/3eSL/2u7MkZ0pt8a+TrYF3aT2pFVemTS3how==",
|
"integrity": "sha512-HDLWbmTnXxhE1MrMgSWjVgdRt+bVYHvfNbW3GTsyIokRSqTHonUTrxV3RhpPDjGIWaHt+ELtDCTYCtUFgL2/Nw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1547,9 +1575,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.7",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -1726,9 +1754,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.4.5",
|
"version": "16.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -1751,6 +1779,20 @@
|
|||||||
"node": ">=0.10"
|
"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": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
@@ -1853,6 +1895,23 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/entities": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||||
@@ -1863,13 +1922,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"get-intrinsic": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@@ -1883,6 +1939,18 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
@@ -1920,9 +1988,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.21.1",
|
"version": "4.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
@@ -1944,7 +2012,7 @@
|
|||||||
"methods": "~1.1.2",
|
"methods": "~1.1.2",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "0.1.10",
|
"path-to-regexp": "0.1.12",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "6.13.0",
|
"qs": "6.13.0",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
@@ -1959,6 +2027,10 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express-fileupload": {
|
"node_modules/express-fileupload": {
|
||||||
@@ -2419,16 +2491,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
|
||||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"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-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.0.0",
|
||||||
"function-bind": "^1.1.2",
|
"function-bind": "^1.1.2",
|
||||||
"has-proto": "^1.0.1",
|
"gopd": "^1.2.0",
|
||||||
"has-symbols": "^1.0.3",
|
"has-symbols": "^1.1.0",
|
||||||
"hasown": "^2.0.0"
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -2483,13 +2560,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"get-intrinsic": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -2570,25 +2644,10 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/has-symbols": {
|
||||||
"version": "1.0.3",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -2757,9 +2816,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.15.1",
|
"version": "2.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz",
|
||||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
"integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
@@ -3036,12 +3095,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lilconfig": {
|
"node_modules/lilconfig": {
|
||||||
"version": "2.1.0",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||||
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
|
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antonk52"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lines-and-columns": {
|
"node_modules/lines-and-columns": {
|
||||||
@@ -3199,6 +3261,15 @@
|
|||||||
"semver": "bin/semver.js"
|
"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": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@@ -3273,9 +3344,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mime-db": {
|
"node_modules/mime-db": {
|
||||||
"version": "1.52.0",
|
"version": "1.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
"integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
@@ -3293,6 +3364,15 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/mimic-response": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
|
||||||
@@ -3590,9 +3670,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
|
||||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
@@ -3655,9 +3735,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemon": {
|
"node_modules/nodemon": {
|
||||||
"version": "3.1.7",
|
"version": "3.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
|
||||||
"integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==",
|
"integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4036,9 +4116,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.10",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pause": {
|
"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": {
|
"node_modules/postcss-load-config/node_modules/yaml": {
|
||||||
"version": "2.6.1",
|
"version": "2.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
|
||||||
@@ -4654,12 +4722,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.9",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz",
|
||||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
"integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.13.0",
|
"is-core-module": "^2.16.0",
|
||||||
"path-parse": "^1.0.7",
|
"path-parse": "^1.0.7",
|
||||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
},
|
},
|
||||||
@@ -4913,15 +4981,69 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.0.6",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.7",
|
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"get-intrinsic": "^1.2.4",
|
"object-inspect": "^1.13.3",
|
||||||
"object-inspect": "^1.13.1"
|
"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": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -4995,6 +5117,23 @@
|
|||||||
"ws": "~8.17.1"
|
"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": {
|
"node_modules/socket.io-parser": {
|
||||||
"version": "4.2.4",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||||
@@ -5008,6 +5147,40 @@
|
|||||||
"node": ">=10.0.0"
|
"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": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -5325,9 +5498,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sweetalert2": {
|
"node_modules/sweetalert2": {
|
||||||
"version": "11.14.5",
|
"version": "11.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.15.0.tgz",
|
||||||
"integrity": "sha512-8MWk5uc/r6bWhiJWkUXyEuApfXAhSCZT8FFX7pZXL7YwaPxq+9Ynhi2dUzWkOFn9jvLjKj22CXuccZ+IHcnjvQ==",
|
"integrity": "sha512-34Xs0CFBac6I1cGG9d+XaBqJrp0F/0prr8rMYOcU0shU/XmkGkRtlCxWNi7PdKYGw9Qf6aoEHNYicX3au37nkw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@@ -5335,9 +5508,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/systeminformation": {
|
"node_modules/systeminformation": {
|
||||||
"version": "5.23.5",
|
"version": "5.23.13",
|
||||||
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.23.5.tgz",
|
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.23.13.tgz",
|
||||||
"integrity": "sha512-PEpJwhRYxZgBCAlWZhWIgfMTjXLqfcaZ1pJsJn9snWNfBW/Z1YQg1mbIUSWrEV3ErAHF7l/OoVLQeaZDlPzkpA==",
|
"integrity": "sha512-4Cn39sTXp7eN9rV/60aNdCXgpQ5xPcUUPIbwJjqTKj4/bNcSYtgZvFdXvZj6pRHP4h17uk6MfdMP5Fm6AK/b0Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"os": [
|
"os": [
|
||||||
"darwin",
|
"darwin",
|
||||||
@@ -5361,9 +5534,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.15",
|
"version": "3.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz",
|
||||||
"integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==",
|
"integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
@@ -5375,7 +5548,7 @@
|
|||||||
"glob-parent": "^6.0.2",
|
"glob-parent": "^6.0.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
"jiti": "^1.21.6",
|
"jiti": "^1.21.6",
|
||||||
"lilconfig": "^2.1.0",
|
"lilconfig": "^3.1.3",
|
||||||
"micromatch": "^4.0.8",
|
"micromatch": "^4.0.8",
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@cdn-app/insider-myaxrin-labs-dinawo",
|
"name": "@cdn-app/insider-myaxrin-labs-dinawo",
|
||||||
"version": "1.0.0-beta.17",
|
"version": "1.1.0-beta.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
387
public/css/paramadminsettingsetup.styles.css
Normal file
387
public/css/paramadminsettingsetup.styles.css
Normal 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;
|
||||||
|
}
|
||||||
@@ -725,4 +725,14 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur lors du chargement:', error);
|
console.error('Erreur lors du chargement:', error);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch('/build-metadata')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
document.getElementById('version-number').textContent = data.build_version;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching version:', error);
|
||||||
|
document.getElementById('version-number').textContent = 'Version indisponible';
|
||||||
});
|
});
|
||||||
@@ -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';
|
||||||
|
});
|
||||||
682
public/js/paramadminsettingsetup.script.js
Normal file
682
public/js/paramadminsettingsetup.script.js
Normal 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();
|
||||||
|
}
|
||||||
@@ -2,20 +2,20 @@ const express = require('express');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fileUpload = require('express-fileupload');
|
|
||||||
const authMiddleware = require('../../../Middlewares/authMiddleware');
|
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const crypto = require('crypto');
|
const { logger } = require('../../../config/logs');
|
||||||
const os = require('os');
|
|
||||||
const osUtils = require('os-utils');
|
// Utilitaires pour la gestion des services
|
||||||
const Convert = require('ansi-to-html');
|
const services = {
|
||||||
const convert = new Convert();
|
fileCleanup: require('../../../services/fileCleanupService'),
|
||||||
const { getUserData, getSetupData } = require('../../../Middlewares/watcherMiddleware');
|
reportManager: require('../../../services/reportService')
|
||||||
|
};
|
||||||
|
|
||||||
router.use(bodyParser.json());
|
router.use(bodyParser.json());
|
||||||
|
|
||||||
|
// Fonction de nettoyage des objets
|
||||||
function clean(obj) {
|
function clean(obj) {
|
||||||
for (var propName in obj) {
|
for (let propName in obj) {
|
||||||
if (obj[propName] === null || obj[propName] === undefined || obj[propName] === '') {
|
if (obj[propName] === null || obj[propName] === undefined || obj[propName] === '') {
|
||||||
delete obj[propName];
|
delete obj[propName];
|
||||||
} else if (typeof obj[propName] === 'object') {
|
} else if (typeof obj[propName] === 'object') {
|
||||||
@@ -27,6 +27,7 @@ function clean(obj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validation des IPs
|
||||||
function validateIP(ip) {
|
function validateIP(ip) {
|
||||||
if (!ip) return false;
|
if (!ip) return false;
|
||||||
|
|
||||||
@@ -46,9 +47,9 @@ function validateIP(ip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isIPv4(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;
|
if (!ipv4Regex.test(ip)) return false;
|
||||||
|
|
||||||
const parts = ip.split('.');
|
const parts = ip.split('.');
|
||||||
return parts.every(part => {
|
return parts.every(part => {
|
||||||
const num = parseInt(part);
|
const num = parseInt(part);
|
||||||
@@ -61,42 +62,111 @@ function isIPv6(ip) {
|
|||||||
return ipv6Regex.test(ip);
|
return ipv6Regex.test(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
// Fonction pour activer ou désactiver un service
|
||||||
res.status(400).json({ error: 'Bad Request. The request cannot be fulfilled due to bad syntax or missing parameters.' });
|
async function handleServices(newConfig, oldConfig) {
|
||||||
});
|
if (!newConfig.services) return;
|
||||||
|
|
||||||
router.post('/', authMiddleware, async (req, res) => {
|
for (const [serviceName, serviceConfig] of Object.entries(newConfig.services)) {
|
||||||
try {
|
const service = services[serviceName];
|
||||||
let setup = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../data', 'setup.json'), 'utf-8'));
|
if (!service) {
|
||||||
|
logger.warn(`Service ${serviceName} not found`);
|
||||||
if (req.body.allowedIps) {
|
continue;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clean(req.body);
|
const oldServiceConfig = oldConfig?.services?.[serviceName] || {};
|
||||||
setup[0] = {
|
const wasEnabled = oldServiceConfig.enabled === 'on'; // ancien état
|
||||||
...setup[0],
|
const isEnabled = serviceConfig.enabled === 'on'; // nouvel état
|
||||||
...req.body
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
try {
|
||||||
path.join(__dirname, '../../../data', 'setup.json'),
|
logger.info(`Processing service ${serviceName}: wasEnabled=${wasEnabled}, isEnabled=${isEnabled}`);
|
||||||
JSON.stringify(setup, null, 2),
|
|
||||||
'utf-8'
|
|
||||||
);
|
|
||||||
|
|
||||||
res.redirect('/dpanel/dashboard/admin/settingsetup');
|
if (isEnabled && !wasEnabled) {
|
||||||
} catch (err) {
|
await service.updateConfig(serviceConfig);
|
||||||
console.error('Erreur lors de la mise à jour de la configuration:', err);
|
await service.start();
|
||||||
res.status(500).send('Server Error');
|
} else if (!isEnabled && wasEnabled) {
|
||||||
|
await service.stop();
|
||||||
|
} else if (isEnabled && wasEnabled &&
|
||||||
|
JSON.stringify(serviceConfig) !== JSON.stringify(oldServiceConfig)) {
|
||||||
|
await service.stop();
|
||||||
|
await service.updateConfig(serviceConfig);
|
||||||
|
await service.start();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error handling service ${serviceName}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mise à jour de la configuration et gestion des services
|
||||||
|
async function handleServicesUpdate(newConfig, oldConfig) {
|
||||||
|
if (!newConfig.services) return;
|
||||||
|
|
||||||
|
for (const [serviceName, serviceConfig] of Object.entries(newConfig.services)) {
|
||||||
|
const oldServiceConfig = oldConfig?.services?.[serviceName] || {};
|
||||||
|
await handleServices(serviceName, serviceConfig, oldServiceConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route principale pour mettre à jour la configuration
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const setupPath = path.join(__dirname, '../../../data', 'setup.json');
|
||||||
|
const oldConfig = JSON.parse(fs.readFileSync(setupPath, 'utf-8'))[0];
|
||||||
|
|
||||||
|
// Validation et traitement des IPs autorisées
|
||||||
|
if (req.body.allowedIps) {
|
||||||
|
let ipsToProcess = Array.isArray(req.body.allowedIps) ? req.body.allowedIps : req.body.allowedIps[""] || [];
|
||||||
|
req.body.allowedIps = ipsToProcess.filter(ip => validateIP(ip.trim())).map(ip => ip.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
['ldap', 'discord'].forEach(service => {
|
||||||
|
if (req.body[service] && !req.body[service].enabled) {
|
||||||
|
req.body[service] = { enabled: 'off' };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Traitement des services
|
||||||
|
if (req.body.services) {
|
||||||
|
for (const [key, value] of Object.entries(req.body.services)) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
req.body.services[key] = { enabled: 'off', schedule: value }; // Par défaut 'off' et ajouter un schedule si nécessaire
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si l'état n'est pas 'on' ou 'off', on le met à 'off'
|
||||||
|
if (value.enabled !== 'on' && value.enabled !== 'off') {
|
||||||
|
req.body.services[key].enabled = 'off';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Traitement des logs
|
||||||
|
if (req.body.logs) {
|
||||||
|
req.body.logs = {
|
||||||
|
enabled: req.body.logs.enabled || "on",
|
||||||
|
excludePaths: Array.isArray(req.body.logs.excludePaths) ? req.body.logs.excludePaths : [],
|
||||||
|
includeOnly: Array.isArray(req.body.logs.includeOnly) ? req.body.logs.includeOnly : [],
|
||||||
|
levels: Array.isArray(req.body.logs.levels) ? req.body.logs.levels : []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fusion des anciennes et nouvelles configurations
|
||||||
|
const newConfig = { ...oldConfig, ...req.body };
|
||||||
|
clean(newConfig);
|
||||||
|
|
||||||
|
// Mise à jour des services
|
||||||
|
await handleServicesUpdate(newConfig, oldConfig);
|
||||||
|
|
||||||
|
// Sauvegarde de la nouvelle configuration
|
||||||
|
fs.writeFileSync(setupPath, JSON.stringify([newConfig], null, 2));
|
||||||
|
|
||||||
|
res.status(200).json({ message: 'Configuration mise à jour avec succès.' });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Erreur lors de la mise à jour de la configuration:', error);
|
||||||
|
res.status(500).json({ error: 'Erreur interne du serveur.' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
module.exports = router;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const multiparty = require('multiparty');
|
const multiparty = require('multiparty');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
// Limite de taille de fichier à 10 Go
|
// Limite de taille de fichier à 10 Go
|
||||||
const MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024; // 10 Go
|
const MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024; // 10 Go
|
||||||
@@ -19,7 +20,7 @@ router.post('/', (req, res) => {
|
|||||||
maxFilesSize: MAX_FILE_SIZE,
|
maxFilesSize: MAX_FILE_SIZE,
|
||||||
});
|
});
|
||||||
|
|
||||||
form.parse(req, (err, fields, files) => {
|
form.parse(req, async (err, fields, files) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Error parsing the file:', err);
|
console.error('Error parsing the file:', err);
|
||||||
return res.status(400).send('Error during the file upload');
|
return res.status(400).send('Error during the file upload');
|
||||||
@@ -30,12 +31,20 @@ router.post('/', (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const file = files.file[0];
|
const file = files.file[0];
|
||||||
// Modifier le chemin pour être relatif à la racine
|
|
||||||
const userDir = path.join(process.cwd(), 'cdn-files', req.user.name);
|
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 filename = fields.filename ? fields.filename[0] : file.originalFilename;
|
||||||
const filePath = path.join(userDir, filename);
|
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
|
// Crée le répertoire s'il n'existe pas
|
||||||
if (!fs.existsSync(userDir)) {
|
if (!fs.existsSync(userDir)) {
|
||||||
@@ -48,7 +57,7 @@ router.post('/', (req, res) => {
|
|||||||
|
|
||||||
readStream.pipe(writeStream);
|
readStream.pipe(writeStream);
|
||||||
|
|
||||||
readStream.on('end', () => {
|
readStream.on('end', async () => {
|
||||||
// Supprimer le fichier temporaire
|
// Supprimer le fichier temporaire
|
||||||
fs.unlinkSync(file.path);
|
fs.unlinkSync(file.path);
|
||||||
|
|
||||||
@@ -57,6 +66,35 @@ router.post('/', (req, res) => {
|
|||||||
if (!fileNamePattern.test(filename)) {
|
if (!fileNamePattern.test(filename)) {
|
||||||
console.warn('Le fichier uploadé ne suit pas le format de nom sécurisé attendu:', filename);
|
console.warn('Le fichier uploadé ne suit pas le format de nom sécurisé attendu:', filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mettre à jour file_info.json si password ou expiryDate présent
|
||||||
|
if (expiryDate || password) {
|
||||||
|
const fileInfo = {
|
||||||
|
fileName: filename,
|
||||||
|
expiryDate: expiryDate,
|
||||||
|
password: hashedPassword,
|
||||||
|
Id: req.user.id,
|
||||||
|
path: filePath
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let data = [];
|
||||||
|
const fileInfoPath = path.join(__dirname, '../../../data', 'file_info.json');
|
||||||
|
|
||||||
|
if (fs.existsSync(fileInfoPath)) {
|
||||||
|
const existingData = await fs.promises.readFile(fileInfoPath, 'utf8');
|
||||||
|
data = JSON.parse(existingData);
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
data = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push(fileInfo);
|
||||||
|
await fs.promises.writeFile(fileInfoPath, JSON.stringify(data, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating file_info.json:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.status(200).send({
|
res.status(200).send({
|
||||||
message: 'File uploaded successfully.',
|
message: 'File uploaded successfully.',
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ router.get('/', authMiddleware, async (req, res) => {
|
|||||||
|
|
||||||
const availableExtensions = Array.from(new Set(fileDetails.map(file => file.extension)));
|
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) {
|
} catch (err) {
|
||||||
console.error('Error reading directory:', err);
|
console.error('Error reading directory:', err);
|
||||||
return res.render('error-recovery-file', { error: err.message });
|
return res.render('error-recovery-file', { error: err.message });
|
||||||
|
|||||||
263
server.js
263
server.js
@@ -1,151 +1,176 @@
|
|||||||
|
require('dotenv').config();
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
const passport = require('passport');
|
const passport = require('passport');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const { logger, logRequestInfo, ErrorLogger } = require('./config/logs');
|
const path = require('path');
|
||||||
const path = require("path");
|
|
||||||
const { version } = require('./package.json');
|
|
||||||
const axios = require('axios');
|
|
||||||
const flash = require('connect-flash');
|
const flash = require('connect-flash');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const fs = require('fs');
|
const fs = require('fs').promises;
|
||||||
const SystemReport = require('./models/reportManager.js');
|
|
||||||
const routes = require('./routes/routes.js');
|
|
||||||
const cron = require('node-cron');
|
const cron = require('node-cron');
|
||||||
const chalk = require('chalk');
|
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 app = express();
|
||||||
|
const PORT = process.env.PORT || 5053;
|
||||||
|
|
||||||
app.set('trust proxy', 1);
|
// Import des modèles requis
|
||||||
|
|
||||||
require('./models/fileCreated.js');
|
require('./models/fileCreated.js');
|
||||||
|
|
||||||
let setup;
|
// Lecture du fichier de configuration
|
||||||
try {
|
const loadSetup = async () => {
|
||||||
setup = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'setup.json'), 'utf8'));
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error reading setup.json:', err);
|
|
||||||
setup = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setup.discord !== undefined) {
|
|
||||||
require('./models/Passport-Discord.js');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setup.ldap !== undefined) {
|
|
||||||
require('./models/Passport-ActiveDirectory.js');
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
|
||||||
|
|
||||||
app.get(['/data/user.json', '/data/file_info.json', '/data/setup.json'], (req, res) => {
|
|
||||||
res.status(403).json({ error: 'Access Denied. You do not have permission to access this resource.' });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(express.urlencoded({ extended: true }));
|
|
||||||
|
|
||||||
function generateSecretKey() {
|
|
||||||
return crypto.randomBytes(64).toString('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(session({
|
|
||||||
secret: generateSecretKey(),
|
|
||||||
resave: false,
|
|
||||||
saveUninitialized: true,
|
|
||||||
cookie: { secure: false }
|
|
||||||
}));
|
|
||||||
|
|
||||||
app.use(passport.initialize());
|
|
||||||
app.use(passport.session());
|
|
||||||
|
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
|
||||||
app.use(bodyParser.json());
|
|
||||||
app.use(flash());
|
|
||||||
|
|
||||||
app.use('/public', express.static(path.join(__dirname, 'public')));
|
|
||||||
app.set('view engine', 'ejs');
|
|
||||||
app.set('views', __dirname + '/views');
|
|
||||||
app.use(routes);
|
|
||||||
|
|
||||||
app.use(logRequestInfo);
|
|
||||||
|
|
||||||
cron.schedule('00 03 * * *', async () => {
|
|
||||||
try {
|
try {
|
||||||
const report = await SystemReport.generate();
|
const setupPath = path.join(__dirname, 'data', 'setup.json');
|
||||||
if (report !== null) {
|
const setupData = await fs.readFile(setupPath, 'utf8');
|
||||||
logger.info('System error report generated successfully');
|
return JSON.parse(setupData);
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ErrorLogger.error('Error generating report :', err);
|
logger.error('Error reading setup.json:', err);
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
cron.schedule('0 * * * *', async () => {
|
// Configuration de l'application
|
||||||
try {
|
const configureApp = async () => {
|
||||||
const fileInfoData = await fs.promises.readFile(path.join(__dirname, '/data/', 'file_info.json'), 'utf8');
|
const setup = await loadSetup();
|
||||||
const fileInfo = JSON.parse(fileInfoData);
|
|
||||||
|
|
||||||
const now = new Date();
|
// Configuration des stratégies d'authentification
|
||||||
|
if (setup.discord) require('./models/Passport-Discord.js');
|
||||||
|
if (setup.ldap) require('./models/Passport-ActiveDirectory.js');
|
||||||
|
|
||||||
for (let index = fileInfo.length - 1; index >= 0; index--) {
|
// Middleware de base
|
||||||
const file = fileInfo[index];
|
app.set('trust proxy', 1);
|
||||||
const expiry = new Date(file.expiryDate);
|
app.set('view engine', 'ejs');
|
||||||
|
app.set('views', path.join(__dirname, 'views'));
|
||||||
|
|
||||||
if ((file.expiryDate && expiry <= now) || !(await fileExists(file.path))) {
|
// Gestionnaire de favicon.ico
|
||||||
fileInfo.splice(index, 1);
|
app.get('/favicon.ico', (req, res) => {
|
||||||
}
|
res.status(204).end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Protection des fichiers sensibles
|
||||||
|
app.get(['/data/user.json', '/data/file_info.json', '/data/setup.json'],
|
||||||
|
(req, res) => res.status(403).json({ error: 'Access Denied' }));
|
||||||
|
|
||||||
|
// Configuration des middlewares
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
app.use('/public', express.static(path.join(__dirname, 'public')));
|
||||||
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
app.use(session({
|
||||||
|
secret: crypto.randomBytes(64).toString('hex'),
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
cookie: {
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
maxAge: 24 * 60 * 60 * 1000
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
app.use(passport.initialize());
|
||||||
|
app.use(passport.session());
|
||||||
|
app.use(flash());
|
||||||
|
app.use(logRequestInfo);
|
||||||
|
app.use(routes);
|
||||||
|
|
||||||
logger.info('Successfully checked file expirations and updated file_info.json');
|
app.use((req, res, next) => {
|
||||||
} catch (err) {
|
if (req.accepts('html')) {
|
||||||
ErrorLogger.error(`Failed to check file expirations: ${err}`);
|
res.status(404).render('unauthorized', { url: req.url });
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function fileExists(filePath) {
|
|
||||||
try {
|
|
||||||
await fs.promises.access(filePath);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllFiles(dirPath, arrayOfFiles) {
|
|
||||||
const files = fs.readdirSync(dirPath);
|
|
||||||
|
|
||||||
arrayOfFiles = arrayOfFiles || [];
|
|
||||||
|
|
||||||
files.forEach(function(file) {
|
|
||||||
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
|
|
||||||
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles);
|
|
||||||
} else {
|
} else {
|
||||||
arrayOfFiles.push(path.join(dirPath, "/", file));
|
// Pour les requêtes API ou autres
|
||||||
|
res.status(404).json({ error: 'Not Found' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return arrayOfFiles;
|
app.use((err, req, res, next) => {
|
||||||
}
|
ErrorLogger.error('Unhandled error:', err);
|
||||||
|
|
||||||
|
res.status(500).json(response);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const allFiles = getAllFiles(__dirname);
|
process.removeAllListeners('warning');
|
||||||
|
process.on('warning', (warning) => {
|
||||||
|
if (warning.name === 'DeprecationWarning' && warning.message.includes('punycode')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.warn(warning.name, warning.message);
|
||||||
|
});
|
||||||
|
|
||||||
const SERVER = process.env.PORT || 5053;
|
// Configuration des tâches planifiées
|
||||||
app.listen(SERVER, () => {
|
const configureCronJobs = () => {
|
||||||
SERVER.timeout = 300000
|
|
||||||
allFiles.forEach(file => {
|
|
||||||
|
|
||||||
console.log(`[ ${chalk.green('OK')} ] Loaded file: ${file}`);
|
// Service de nettoyage des fichiers
|
||||||
|
fileCleanup.start();
|
||||||
|
|
||||||
|
// Service de rreport système
|
||||||
|
reportManager.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Utilitaires pour la vérification des fichiers
|
||||||
|
const fileUtils = {
|
||||||
|
async fileExists(filePath) {
|
||||||
|
try {
|
||||||
|
await fs.access(filePath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Démarrage du serveur
|
||||||
|
const startServer = () => {
|
||||||
|
const server = app.listen(PORT, () => {
|
||||||
|
server.timeout = 300000;
|
||||||
|
console.clear()
|
||||||
|
|
||||||
|
if (logger) {
|
||||||
|
logger.info('☀️ Welcome to the Content Delivery Network Server');
|
||||||
|
logger.info(`🚀 Server running on port ${PORT}`);
|
||||||
|
logger.info(`⚜️ Application developed by Dinawo, part of the Myaxrin Labs group`);
|
||||||
|
logger.info(`♨️ Version: ${version}`);
|
||||||
|
} else {
|
||||||
|
console.error('🔴 Logger is not initialized');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.clear();
|
return server;
|
||||||
if (logger) {
|
};
|
||||||
logger.info(`☀️ Welcome to the Content Delivery Network Server`);
|
|
||||||
logger.info(`🚀 Your server is available and running on port ${SERVER}`);
|
// Gestion des erreurs globales
|
||||||
logger.info(`⚜️ Application developed by Dinawo, part of the Myaxrin Labs group`);
|
const configureErrorHandling = () => {
|
||||||
logger.info(`♨️ Version: ${version}`);
|
process.on('uncaughtException', (err) => {
|
||||||
console.log('');
|
ErrorLogger.error('Uncaught Exception:', err);
|
||||||
} else {
|
});
|
||||||
console.error('🔴 Logger is not initialized');
|
|
||||||
|
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
131
services/BaseService.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
const { logger } = require('../config/logs');
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
class BaseService {
|
||||||
|
constructor(name, defaultConfig = {}) {
|
||||||
|
this.name = name;
|
||||||
|
this.defaultConfig = defaultConfig;
|
||||||
|
this.config = {};
|
||||||
|
this.isRunning = false;
|
||||||
|
this.status = 'stopped';
|
||||||
|
this.logger = logger;
|
||||||
|
this.setupPath = path.join(process.cwd(), 'data', 'setup.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadConfig() {
|
||||||
|
try {
|
||||||
|
const setupContent = await fs.readFile(this.setupPath, 'utf8');
|
||||||
|
const setup = JSON.parse(setupContent);
|
||||||
|
|
||||||
|
// Retrieve the service configuration from setup.json
|
||||||
|
const serviceConfig = setup[0]?.services?.[this.name] || {};
|
||||||
|
|
||||||
|
// Merge with the default configuration
|
||||||
|
this.config = {
|
||||||
|
...this.defaultConfig,
|
||||||
|
...serviceConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.config;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error loading configuration for ${this.name}:`, error);
|
||||||
|
this.config = this.defaultConfig;
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
if (this.isRunning) {
|
||||||
|
this.logger.warn(`Service ${this.name} is already running`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load the configuration before starting
|
||||||
|
await this.loadConfig();
|
||||||
|
|
||||||
|
// Check if the service is enabled
|
||||||
|
if (this.config.enabled !== 'on') {
|
||||||
|
this.logger.info(`Service ${this.name} is not enabled in the configuration`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this._startImplementation();
|
||||||
|
this.isRunning = true;
|
||||||
|
this.status = 'running';
|
||||||
|
this.logger.info(`Service ${this.name} started successfully`);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error starting service ${this.name}:`, error);
|
||||||
|
this.status = 'error';
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop() {
|
||||||
|
if (!this.isRunning) {
|
||||||
|
this.logger.warn(`Service ${this.name} is not running`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._stopImplementation();
|
||||||
|
this.isRunning = false;
|
||||||
|
this.status = 'stopped';
|
||||||
|
this.logger.info(`Service ${this.name} stopped successfully`);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error stopping service ${this.name}:`, error);
|
||||||
|
this.status = 'error';
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatus() {
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
status: this.status,
|
||||||
|
isRunning: this.isRunning,
|
||||||
|
config: this.config
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateConfig(newConfig) {
|
||||||
|
// Load the current configuration from setup.json
|
||||||
|
let setup;
|
||||||
|
try {
|
||||||
|
const setupContent = await fs.readFile(this.setupPath, 'utf8');
|
||||||
|
setup = JSON.parse(setupContent);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error reading setup.json: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the service configuration
|
||||||
|
if (!setup[0].services) {
|
||||||
|
setup[0].services = {};
|
||||||
|
}
|
||||||
|
setup[0].services[this.name] = {
|
||||||
|
...this.config,
|
||||||
|
...newConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save to setup.json
|
||||||
|
try {
|
||||||
|
await fs.writeFile(this.setupPath, JSON.stringify(setup, null, 2));
|
||||||
|
this.config = setup[0].services[this.name];
|
||||||
|
this.logger.info(`Configuration for service ${this.name} updated:`, this.config);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error saving configuration: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These methods must be implemented by subclasses
|
||||||
|
async _startImplementation() {
|
||||||
|
throw new Error('_startImplementation must be implemented by the subclass');
|
||||||
|
}
|
||||||
|
|
||||||
|
async _stopImplementation() {
|
||||||
|
throw new Error('_stopImplementation must be implemented by the subclass');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BaseService;
|
||||||
141
services/fileCleanupService.js
Normal file
141
services/fileCleanupService.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
const BaseService = require('./BaseService');
|
||||||
|
const cron = require('node-cron');
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
class FileCleanupService extends BaseService {
|
||||||
|
constructor(config = {}) {
|
||||||
|
super('fileCleanup', {
|
||||||
|
fileInfoPath: config.fileInfoPath || path.join(process.cwd(), 'data', 'file_info.json'),
|
||||||
|
cronSchedule: config.cronSchedule || '0 * * * *',
|
||||||
|
...config
|
||||||
|
});
|
||||||
|
this.job = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _startImplementation() {
|
||||||
|
if (this.job) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valider le cron schedule
|
||||||
|
if (!cron.validate(this.config.cronSchedule)) {
|
||||||
|
throw new Error(`Schedule cron invalide: ${this.config.cronSchedule}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.job = cron.schedule(this.config.cronSchedule, () => this.cleanup());
|
||||||
|
}
|
||||||
|
|
||||||
|
async _stopImplementation() {
|
||||||
|
if (this.job) {
|
||||||
|
this.job.stop();
|
||||||
|
this.job = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Réutiliser vos méthodes existantes
|
||||||
|
async fileExists(filepath) {
|
||||||
|
try {
|
||||||
|
await fs.access(filepath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePath(filepath) {
|
||||||
|
return path.normalize(filepath).replace(/\\/g, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFileInfo() {
|
||||||
|
try {
|
||||||
|
const data = await fs.readFile(this.config.fileInfoPath, 'utf8');
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error('Erreur lors de la lecture du fichier info:', err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveFileInfo(fileInfo) {
|
||||||
|
try {
|
||||||
|
await fs.writeFile(
|
||||||
|
this.config.fileInfoPath,
|
||||||
|
JSON.stringify(fileInfo, null, 2),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error('Erreur lors de la sauvegarde du fichier info:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async processFile(file, now) {
|
||||||
|
const normalizedPath = this.normalizePath(file.path);
|
||||||
|
|
||||||
|
if (file.expiryDate && new Date(file.expiryDate) <= now) {
|
||||||
|
return { status: 'expired', file: { ...file, path: normalizedPath } };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await this.fileExists(normalizedPath))) {
|
||||||
|
return { status: 'missing', file: { ...file, path: normalizedPath } };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: 'valid', file };
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanup() {
|
||||||
|
try {
|
||||||
|
const fileInfo = await this.readFileInfo();
|
||||||
|
const now = new Date();
|
||||||
|
const results = {
|
||||||
|
expired: [],
|
||||||
|
missing: [],
|
||||||
|
processed: 0,
|
||||||
|
remaining: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const validFiles = [];
|
||||||
|
await Promise.all(fileInfo.map(async (file) => {
|
||||||
|
const { status, file: processedFile } = await this.processFile(file, now);
|
||||||
|
|
||||||
|
if (status === 'expired') {
|
||||||
|
results.expired.push(processedFile);
|
||||||
|
results.processed++;
|
||||||
|
} else if (status === 'missing') {
|
||||||
|
results.missing.push(processedFile);
|
||||||
|
results.processed++;
|
||||||
|
} else {
|
||||||
|
validFiles.push(file);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (results.processed > 0) {
|
||||||
|
results.remaining = validFiles.length;
|
||||||
|
await this.saveFileInfo(validFiles);
|
||||||
|
|
||||||
|
this.logger.info('Nettoyage des fichiers terminé', {
|
||||||
|
expired: results.expired.length,
|
||||||
|
missing: results.missing.length,
|
||||||
|
totalProcessed: results.processed,
|
||||||
|
remainingFiles: results.remaining
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error('Erreur lors du nettoyage des fichiers:', {
|
||||||
|
error: err.message,
|
||||||
|
stack: err.stack
|
||||||
|
});
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async forceCleanup() {
|
||||||
|
return this.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new FileCleanupService();
|
||||||
181
services/reportService.js
Normal file
181
services/reportService.js
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
const BaseService = require('./BaseService');
|
||||||
|
const cron = require('node-cron');
|
||||||
|
const os = require('os');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const ip = require('ip');
|
||||||
|
const si = require('systeminformation');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const packageJson = require('../package.json');
|
||||||
|
|
||||||
|
class ReportManagerService extends BaseService {
|
||||||
|
constructor() {
|
||||||
|
// Configuration par défaut
|
||||||
|
const defaultConfig = {
|
||||||
|
enabled: 'off',
|
||||||
|
endpoint: 'https://cdn-apollon-p198-61m1.dinawo.fr/api/report/receive',
|
||||||
|
cronSchedule: '0 0 * * *' // Par défaut, une fois par jour à minuit
|
||||||
|
};
|
||||||
|
|
||||||
|
super('reportManager', defaultConfig);
|
||||||
|
this.job = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _startImplementation() {
|
||||||
|
if (this.job) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valider le cron schedule
|
||||||
|
const schedule = this.config.cronSchedule || this.defaultConfig.cronSchedule;
|
||||||
|
if (!cron.validate(schedule)) {
|
||||||
|
throw new Error(`Schedule cron invalide: ${schedule}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.job = cron.schedule(schedule, () => this.generateAndSendReport());
|
||||||
|
this.logger.info(`Service de rapport programmé avec le planning: ${schedule}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _stopImplementation() {
|
||||||
|
if (this.job) {
|
||||||
|
this.job.stop();
|
||||||
|
this.job = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatUptime(uptime) {
|
||||||
|
const days = Math.floor(uptime / (24 * 60 * 60));
|
||||||
|
uptime %= (24 * 60 * 60);
|
||||||
|
const hours = Math.floor(uptime / (60 * 60));
|
||||||
|
uptime %= (60 * 60);
|
||||||
|
const minutes = Math.floor(uptime / 60);
|
||||||
|
return `${days}d ${hours}h ${minutes}m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInternalErrors() {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
const previousDate = date.toISOString().split('T')[0];
|
||||||
|
const logFile = path.join(__dirname, '..', 'logs', `log-${previousDate}.log`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const logs = fs.readFileSync(logFile, 'utf-8');
|
||||||
|
return logs.split('\n').filter(line => /\[38;5;9mInternal-Error/.test(line));
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error('Erreur lors de la lecture des logs:', err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateReport() {
|
||||||
|
try {
|
||||||
|
const internalErrors = await this.getInternalErrors();
|
||||||
|
|
||||||
|
if (internalErrors.length === 0) {
|
||||||
|
this.logger.info('Pas d\'erreurs internes dans les logs d\'hier. Pas de rapport généré.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadavg = os.loadavg().map(load => (load / os.cpus().length) * 100);
|
||||||
|
|
||||||
|
const osInfo = {
|
||||||
|
type: os.type(),
|
||||||
|
platform: os.platform(),
|
||||||
|
arch: os.arch(),
|
||||||
|
release: os.release(),
|
||||||
|
uptime: this.formatUptime(os.uptime()),
|
||||||
|
loadavg: loadavg
|
||||||
|
};
|
||||||
|
|
||||||
|
const networkInterfaces = os.networkInterfaces();
|
||||||
|
const diskUsage = await si.fsSize();
|
||||||
|
const cpuTemperature = await si.cpuTemperature();
|
||||||
|
const userInfo = os.userInfo();
|
||||||
|
|
||||||
|
const systemInfo = {
|
||||||
|
memoryInfo: ((os.totalmem() - os.freemem()) / os.totalmem() * 100).toFixed(2),
|
||||||
|
cpuInfo: (os.cpus().length / os.cpus().length * 100).toFixed(2),
|
||||||
|
diskInfo: diskUsage.map(disk => ({
|
||||||
|
fs: disk.fs,
|
||||||
|
type: disk.type,
|
||||||
|
size: disk.size,
|
||||||
|
used: disk.used,
|
||||||
|
available: disk.available,
|
||||||
|
use: disk.use
|
||||||
|
})),
|
||||||
|
ipAddress: ip.address(),
|
||||||
|
cdnVersion: packageJson.version,
|
||||||
|
osInfo: osInfo,
|
||||||
|
userInfo: {
|
||||||
|
username: userInfo.username,
|
||||||
|
homedir: userInfo.homedir,
|
||||||
|
shell: userInfo.shell
|
||||||
|
},
|
||||||
|
errors: internalErrors,
|
||||||
|
networkInterfaces: networkInterfaces,
|
||||||
|
serverUptime: os.uptime(),
|
||||||
|
systemLoad: loadavg,
|
||||||
|
cpuTemperature: cpuTemperature
|
||||||
|
};
|
||||||
|
|
||||||
|
const reportDate = new Date();
|
||||||
|
reportDate.setDate(reportDate.getDate() - 1);
|
||||||
|
const reportFileName = `report_${reportDate.toISOString().split('T')[0]}_${ip.address()}.json`;
|
||||||
|
|
||||||
|
// Sauvegarder le rapport localement
|
||||||
|
const reportDir = path.join(__dirname, '..', 'report');
|
||||||
|
if (!fs.existsSync(reportDir)) {
|
||||||
|
fs.mkdirSync(reportDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(reportDir, reportFileName),
|
||||||
|
JSON.stringify(systemInfo, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
return systemInfo;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Erreur lors de la génération du rapport:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateAndSendReport() {
|
||||||
|
try {
|
||||||
|
const report = await this.generateReport();
|
||||||
|
|
||||||
|
if (!report) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info("Envoi du rapport...");
|
||||||
|
|
||||||
|
const response = await fetch(this.config.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(report)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Erreur HTTP! statut: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await response.json();
|
||||||
|
this.logger.info('Rapport envoyé avec succès. Réponse:', responseData);
|
||||||
|
|
||||||
|
return responseData;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Échec de l\'envoi du rapport:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode pour forcer l'envoi d'un rapport immédiatement
|
||||||
|
async forceSendReport() {
|
||||||
|
return this.generateAndSendReport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new ReportManagerService();
|
||||||
@@ -69,14 +69,25 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu dropdown-menu-right" id="accountDropdownMenu">
|
<div class="dropdown-menu dropdown-menu-right" id="accountDropdownMenu">
|
||||||
<a class="dropdown-item" href="/dpanel/dashboard/profil">
|
<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>
|
</a>
|
||||||
|
<% if (user.role === 'admin') { %>
|
||||||
<a class="dropdown-item" href="/dpanel/dashboard/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>
|
</a>
|
||||||
|
<% } %>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item" href="/auth/logout">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -152,7 +163,7 @@
|
|||||||
|
|
||||||
<footer class="footer mt-auto py-3 bg-light">
|
<footer class="footer mt-auto py-3 bg-light">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span class="text-muted">Version: 1.0.0-beta.17 | © 2024 Myaxrin Labs</span>
|
<span class="text-muted">Version: <span id="version-number">...</span> | © 2024 Myaxrin Labs</span>
|
||||||
<a href="#" class="float-right" onclick="displayMetadata()">Metadata</a>
|
<a href="#" class="float-right" onclick="displayMetadata()">Metadata</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -190,10 +190,10 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<footer class="py-3 my-4">
|
<footer class="py-3 my-4">
|
||||||
<ul class="nav justify-content-center border-bottom pb-3 mb-3">
|
<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>
|
</ul>
|
||||||
<p class="text-center text-muted">© 2024 Myaxrin Labs</p>
|
<p class="text-center text-muted">© 2024 Myaxrin Labs</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -4,12 +4,20 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Parameter Admin</title>
|
<title>Parameter Admin</title>
|
||||||
|
|
||||||
<link rel="icon" href="/public/assets/homelab_logo.png" />
|
<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 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="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>
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
|
||||||
<style>
|
|
||||||
|
<style>
|
||||||
|
|
||||||
body {
|
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-image: url('<%= user.wallpaper %>');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
@@ -17,448 +25,376 @@
|
|||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow-y: auto;
|
display: flex;
|
||||||
}
|
flex-direction: column;
|
||||||
@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;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="animate">
|
<body class="animate">
|
||||||
<div id="app" class="min-h-screen flex items-center justify-center">
|
<div id="app" class="min-h-screen">
|
||||||
<div class="container mt-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<h1 class="text-3xl font-semibold mb-6 text-center animate">Gestion de la configuration</h1>
|
<!-- 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>
|
||||||
|
|
||||||
<div class="form-container">
|
<!-- Tabs Navigation -->
|
||||||
<form id="setupForm" action="/api/dpanel/dashboard/admin/update-setup" method="POST" class="mb-4 animate">
|
<div class="tabs">
|
||||||
<h2 class="text-2xl font-semibold mb-4">Paramètres LDAP</h2>
|
<button class="tab active" data-tab="general">
|
||||||
<div class="form-group animate flex items-center justify-between">
|
<i class="fas fa-cog"></i>
|
||||||
<label for="ldapEnabled" class="block mb-2">Activer LDAP :</label>
|
<span>Général</span>
|
||||||
<label class="switch">
|
</button>
|
||||||
<input type="checkbox" id="ldapEnabled" name="ldap[enabled]" <%= setup.ldap && setup.ldap.enabled === 'on' ? 'checked' : '' %> onchange="toggleForm('ldapForm', this)">
|
<button class="tab" data-tab="authentication">
|
||||||
<span class="slider"></span>
|
<i class="fas fa-lock"></i>
|
||||||
</label>
|
<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>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<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 id="ldapForm" style="display: <%= setup.ldap && setup.ldap.enabled === 'on' ? 'block' : 'none' %>">
|
</div>
|
||||||
<div class="form-group animate">
|
|
||||||
<label for="ldapUrl" class="block mb-2">URL :</label>
|
<!-- Authentication Tab -->
|
||||||
<input type="text" id="ldapUrl" name="ldap[url]" class="form-control" value="<%= setup.ldap ? setup.ldap.url : '' %>">
|
<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>
|
||||||
<div class="form-group animate">
|
<div class="form-group flex items-center justify-between">
|
||||||
<label for="ldapBaseDN" class="block mb-2">Base DN :</label>
|
<label for="ldapEnabled">Activer LDAP :</label>
|
||||||
<input type="text" id="ldapBaseDN" name="ldap[baseDN]" class="form-control" value="<%= setup.ldap ? setup.ldap.baseDN : '' %>">
|
<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>
|
||||||
<div class="form-group animate">
|
<div id="ldapForm" class="nested-settings" style="display: <%= setup.ldap?.enabled === 'on' ? 'block' : 'none' %>">
|
||||||
<label for="ldapUsername" class="block mb-2">Nom d'utilisateur :</label>
|
<div class="form-group">
|
||||||
<input type="text" id="ldapUsername" name="ldap[username]" class="form-control" value="<%= setup.ldap ? setup.ldap.username : '' %>">
|
<label for="ldapUrl">URL :</label>
|
||||||
</div>
|
<input type="text" id="ldapUrl" name="ldap[url]" class="form-control" value="<%= setup.ldap?.url %>">
|
||||||
<div class="form-group animate">
|
</div>
|
||||||
<label for="ldapPassword" class="block mb-2">Mot de passe :</label>
|
<div class="form-group">
|
||||||
<input type="password" id="ldapPassword" name="ldap[password]" class="form-control" value="<%= setup.ldap ? setup.ldap.password : '' %>">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-2xl font-semibold mb-4 mt-8">Paramètres Discord</h2>
|
<!-- Discord Section -->
|
||||||
<div class="form-group animate flex items-center justify-between">
|
<div class="settings-card">
|
||||||
<label for="discordEnabled" class="block mb-2">Activer Discord :</label>
|
<div class="settings-card-header">
|
||||||
<label class="switch">
|
<i class="fab fa-discord text-xl"></i>
|
||||||
<input type="checkbox" id="discordEnabled" name="discord[enabled]" <%= setup.discord && setup.discord.enabled === 'on' ? 'checked' : '' %> onchange="toggleForm('discordForm', this)">
|
<h2 class="settings-card-title">Configuration Discord</h2>
|
||||||
<span class="slider"></span>
|
</div>
|
||||||
</label>
|
<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>
|
||||||
<div id="discordForm" style="<%= `display: ${setup.discord?.enabled === 'on' ? 'block' : 'none'}` %>">
|
</div>
|
||||||
<div class="form-group animate">
|
|
||||||
<label for="discordClientID" class="block mb-2">ID Client :</label>
|
<!-- Services Tab -->
|
||||||
<input type="text" id="discordClientID" name="discord[clientID]" class="form-control" value="<%= setup.discord ? setup.discord.clientID : '' %>">
|
<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>
|
||||||
<div class="form-group animate">
|
<div class="form-group flex items-center justify-between">
|
||||||
<label for="discordClientSecret" class="block mb-2">Secret Client :</label>
|
<div>
|
||||||
<input type="password" id="discordClientSecret" name="discord[clientSecret]" class="form-control" value="<%= setup.discord ? setup.discord.clientSecret : '' %>">
|
<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>
|
||||||
<div class="form-group animate">
|
<div id="fileCleanupConfig" class="nested-settings" style="display: <%= setup.services?.fileCleanup?.enabled === 'on' ? 'block' : 'none' %>">
|
||||||
<label for="discordIdentifyURL" class="block mb-2">URL d'identification :</label>
|
<div class="form-group">
|
||||||
<input type="text" id="discordIdentifyURL" name="discord[identifyURL]" class="form-control" value="<%= setup.discord ? setup.discord.identifyURL : '' %>">
|
<label>Planning (Cron) :</label>
|
||||||
</div>
|
<input type="text"
|
||||||
<div class="form-group animate">
|
name="services[fileCleanup][schedule]"
|
||||||
<label for="discordAuthorizedIDs" class="block mb-2">IDs Autorisés :</label>
|
class="form-control"
|
||||||
<input type="text" id="discordAuthorizedIDs" name="discord[authorizedIDs]" class="form-control" value="<%= setup.discord ? setup.discord.authorizedIDs : '' %>">
|
value="<%= setup.services?.fileCleanup?.schedule || '0 * * * *' %>"
|
||||||
|
placeholder="0 * * * *">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-2xl font-semibold mb-4 mt-8">Autres Paramètres</h2>
|
<!-- Report Manager -->
|
||||||
<div class="form-group animate">
|
<div class="settings-card">
|
||||||
<label for="domain" class="block mb-2">Domaine :</label>
|
<div class="settings-card-header">
|
||||||
<input type="text" id="domain" name="domain" class="form-control" value="<%= setup.domain %>">
|
<i class="fas fa-chart-line text-xl"></i>
|
||||||
</div>
|
<h2 class="settings-card-title">Gestionnaire de rapports</h2>
|
||||||
<div class="form-group animate">
|
</div>
|
||||||
<label for="webhooksDiscord" class="block mb-2">Webhooks Discord :</label>
|
<div class="form-group flex items-center justify-between">
|
||||||
<input type="text" id="webhooksDiscord" name="webhooks_discord" class="form-control" value="<%= setup.webhooks_discord %>">
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group animate">
|
<!-- Security Tab -->
|
||||||
<label class="block mb-2">IPs Autorisées pour les Webhooks :</label>
|
<div class="tab-content" data-tab-content="security">
|
||||||
<div class="space-y-2">
|
<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">
|
<div id="ipList" class="space-y-2">
|
||||||
<% if (setup.allowedIps && setup.allowedIps.length > 0) { %>
|
<% if (setup.allowedIps?.length > 0) { %>
|
||||||
<% setup.allowedIps.forEach((ip, index) => { %>
|
<% setup.allowedIps.forEach(ip => { %>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="ip-entry flex items-center gap-2">
|
||||||
<input type="text" name="allowedIps[]" class="form-control flex-1" value="<%= ip %>">
|
<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>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="ip-entry flex items-center gap-2">
|
||||||
<input type="text" name="allowedIps[]" class="form-control flex-1"
|
<input type="text" name="allowedIps[]" class="form-control flex-1" placeholder="IPv4/IPv6 ou CIDR">
|
||||||
placeholder="IPv4/IPv6 (ex: 192.168.1.1 ou 2001:db8::1 ou CIDR)">
|
<button type="button" class="btn btn-icon" onclick="removeIp(this)">
|
||||||
<button type="button" class="btn btn-secondary p-2" onclick="removeIpField(this)">
|
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-secondary w-full py-2" onclick="addIpField()">
|
<button type="button" class="btn btn-secondary w-full mt-4" onclick="addIp()">
|
||||||
<i class="fas fa-plus mr-2"></i>
|
<i class="fas fa-plus mr-2"></i>Ajouter une IP
|
||||||
Ajouter une IP
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary w-full py-2 mt-4">
|
<!-- 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>
|
||||||
|
|
||||||
|
<!-- 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
|
||||||
|
</a>
|
||||||
|
<button type="submit" form="setupForm" class="btn btn-primary">
|
||||||
<i class="fas fa-save mr-2"></i>
|
<i class="fas fa-save mr-2"></i>
|
||||||
Mettre à jour
|
Enregistrer les modifications
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
<a href="/dpanel/dashboard/admin/" class="btn btn-secondary w-full py-2 mt-4 text-center">
|
|
||||||
<i class="fas fa-arrow-left mr-2"></i>
|
|
||||||
Retour au tableau de bord admin
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="themeSwitcher" class="btn btn-secondary p-2">
|
<!-- Theme Switcher -->
|
||||||
<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">
|
<button id="themeSwitcher" class="theme-switcher">
|
||||||
<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" />
|
<i class="fas fa-sun"></i>
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<script>
|
<script src="/public/js/paramadminsettingsetup.script.js"></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>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -152,9 +152,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</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">
|
<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>
|
</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -163,6 +162,17 @@
|
|||||||
elements.forEach(element => {
|
elements.forEach(element => {
|
||||||
element.classList.add('animate-fade-in-up-visible');
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user