diff --git a/Middlewares/discordWebhookSuspisiousAlertMiddleware.js b/Middlewares/discordWebhookSuspisiousAlertMiddleware.js
index 8ecac13..86bf711 100644
--- a/Middlewares/discordWebhookSuspisiousAlertMiddleware.js
+++ b/Middlewares/discordWebhookSuspisiousAlertMiddleware.js
@@ -32,9 +32,42 @@ function sendDiscordWebhook(url, req, statusCode) {
const allowedIps = setupData[0].allowedIps || [];
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
+ // Skip monitoring for localhost/local IPs
+ const localIps = ['127.0.0.1', '::1', 'localhost', '::ffff:127.0.0.1'];
+ if (localIps.includes(ip)) {
+ return;
+ }
+
+ // Skip monitoring for Chrome DevTools requests
+ if (req.originalUrl.includes('.well-known/appspecific/com.chrome.devtools.json')) {
+ return;
+ }
+
+ // Skip monitoring for legitimate API endpoints
+ const legitimateEndpoints = [
+ '/api/dpanel/dashboard/profilpicture',
+ '/api/dpanel/dashboard/backgroundcustom',
+ '/api/dpanel/collaboration',
+ '/api/dpanel/users/search',
+ '/dpanel/dashboard/profil',
+ '/build-metadata',
+ '/api/dpanel/collaboration/add',
+ '/api/dpanel/collaboration/remove',
+ '/api/dpanel/collaboration/users'
+ ];
+
+ if (legitimateEndpoints.some(endpoint => req.originalUrl.includes(endpoint))) {
+ return;
+ }
+
+ // Skip monitoring if IP is allowed
if (isIpAllowed(ip, allowedIps)) {
return;
- } else {
+ }
+
+ // Skip monitoring for authenticated users on dashboard routes
+ if (req.user && req.originalUrl.startsWith('/dpanel/dashboard')) {
+ return;
}
const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
diff --git a/Middlewares/watcherMiddleware.js b/Middlewares/watcherMiddleware.js
index 70ac5b6..5b4908c 100644
--- a/Middlewares/watcherMiddleware.js
+++ b/Middlewares/watcherMiddleware.js
@@ -3,49 +3,65 @@ const chokidar = require('chokidar');
const fs = require('fs');
const { logger, ErrorLogger, logRequestInfo } = require('../config/logs');
+// Define file paths
const userFilePath = path.resolve(__dirname, '../data/user.json');
const setupFilePath = path.resolve(__dirname, '../data/setup.json');
+const collaborationFilePath = path.resolve(__dirname, '../data/collaboration.json');
-let userData, setupData;
+// Initialize data objects
+let userData, setupData, collaborationData;
+// Load initial user data
try {
userData = JSON.parse(fs.readFileSync(userFilePath, 'utf-8'));
} catch (error) {
ErrorLogger.error(`Error parsing user.json: ${error}`);
}
+// Load initial setup data
try {
setupData = JSON.parse(fs.readFileSync(setupFilePath, 'utf-8'));
} catch (error) {
ErrorLogger.error(`Error parsing setup.json: ${error}`);
}
-const watcher = chokidar.watch([userFilePath, setupFilePath], {
+// Load initial collaboration data
+try {
+ collaborationData = JSON.parse(fs.readFileSync(collaborationFilePath, 'utf-8'));
+} catch (error) {
+ ErrorLogger.error(`Error parsing collaboration.json: ${error}`);
+}
+
+// Set up file watcher
+const watcher = chokidar.watch([userFilePath, setupFilePath, collaborationFilePath], {
persistent: true
});
+// Handle file changes
watcher.on('change', (filePath) => {
let modifiedFile;
- if (filePath === userFilePath) {
- try {
+
+ try {
+ if (filePath === userFilePath) {
userData = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
modifiedFile = 'user.json';
- } catch (error) {
- logger.error(`Error parsing user.json: ${error}`);
- }
- } else if (filePath === setupFilePath) {
- try {
+ } else if (filePath === setupFilePath) {
setupData = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
modifiedFile = 'setup.json';
- } catch (error) {
- logger.error(`Error parsing setup.json: ${error}`);
+ } else if (filePath === collaborationFilePath) {
+ collaborationData = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
+ modifiedFile = 'collaboration.json';
}
+
+ logger.info(`File ${modifiedFile} has been modified`);
+ } catch (error) {
+ ErrorLogger.error(`Error parsing ${modifiedFile}: ${error}`);
}
-
- logger.info(`File ${modifiedFile} has been modified`);
});
+// Export data access functions
module.exports = {
getUserData: () => Promise.resolve(userData),
- getSetupData: () => Promise.resolve(setupData)
+ getSetupData: () => Promise.resolve(setupData),
+ getCollaborationData: () => Promise.resolve(collaborationData)
};
\ No newline at end of file
diff --git a/models/banModel.js b/models/banModel.js
index aa2d92c..0dfc93f 100644
--- a/models/banModel.js
+++ b/models/banModel.js
@@ -10,10 +10,24 @@ const logAndBanSuspiciousActivity = async (req, res, next) => {
const ip = req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const url = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
-if (req.originalUrl === '/auth/activedirectory', "/favicon.ico" && req.method === 'POST') {
+ // Skip monitoring for localhost/local IPs
+ const localIps = ['127.0.0.1', '::1', 'localhost', '::ffff:127.0.0.1'];
+ if (localIps.includes(ip)) {
next();
return;
-}
+ }
+
+ // Skip monitoring for Chrome DevTools requests
+ if (req.originalUrl.includes('.well-known/appspecific/com.chrome.devtools.json')) {
+ next();
+ return;
+ }
+
+ // Skip monitoring for specific endpoints
+ if (req.originalUrl === '/auth/activedirectory' || req.originalUrl === '/favicon.ico') {
+ next();
+ return;
+ }
let bans;
try {
diff --git a/models/websocketManager.js b/models/websocketManager.js
new file mode 100644
index 0000000..f2883e3
--- /dev/null
+++ b/models/websocketManager.js
@@ -0,0 +1,90 @@
+const WebSocket = require('ws');
+const { logger } = require('../config/logs');
+
+class WebSocketManager {
+ constructor(server) {
+ this.wss = new WebSocket.Server({ server });
+ this.connections = new Map(); // Pour stocker les connexions utilisateur
+ this.init();
+ }
+
+ init() {
+ this.wss.on('connection', (ws, req) => {
+
+ ws.on('message', (message) => {
+ try {
+ const data = JSON.parse(message);
+ this.handleMessage(ws, data);
+ } catch (error) {
+ logger.error('Error handling WebSocket message:', error);
+ }
+ });
+
+ ws.on('close', () => {
+ this.handleDisconnect(ws);
+ });
+ });
+ }
+
+ handleMessage(ws, data) {
+ switch (data.type) {
+ case 'join':
+ this.handleJoin(ws, data);
+ break;
+ case 'leave':
+ this.handleLeave(ws, data);
+ break;
+ default:
+ logger.warn('Unknown message type:', data.type);
+ }
+ }
+
+ handleJoin(ws, data) {
+ const { userId, fileId } = data;
+ this.connections.set(ws, { userId, fileId });
+ this.broadcastFileStatus(fileId);
+ }
+
+ handleLeave(ws, data) {
+ const { fileId } = data;
+ this.connections.delete(ws);
+ this.broadcastFileStatus(fileId);
+ }
+
+ handleDisconnect(ws) {
+ const connection = this.connections.get(ws);
+ if (connection) {
+ this.broadcastFileStatus(connection.fileId);
+ this.connections.delete(ws);
+ }
+ }
+
+ broadcastFileStatus(fileId) {
+ const activeUsers = Array.from(this.connections.values())
+ .filter(conn => conn.fileId === fileId)
+ .map(conn => conn.userId);
+
+ const message = JSON.stringify({
+ type: 'fileStatus',
+ fileId,
+ activeUsers
+ });
+
+ this.wss.clients.forEach(client => {
+ if (client.readyState === WebSocket.OPEN) {
+ client.send(message);
+ }
+ });
+ }
+
+ // Méthode pour envoyer une mise à jour à tous les clients
+ broadcast(data) {
+ this.wss.clients.forEach(client => {
+ if (client.readyState === WebSocket.OPEN) {
+ client.send(JSON.stringify(data));
+ }
+ });
+ }
+}
+
+module.exports = WebSocketManager;
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 52d567e..8789650 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@cdn-app/insider-myaxrin-labs-dinawo",
- "version": "1.1.0-beta.1",
+ "version": "1.1.1-beta.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@cdn-app/insider-myaxrin-labs-dinawo",
- "version": "1.1.0-beta.1",
+ "version": "1.1.1-beta.1",
"license": "ISC",
"dependencies": {
"@auth/express": "^0.5.1",
@@ -62,7 +62,8 @@
"tailwindcss": "^3.3.5",
"toastify-js": "^1.12.0",
"winston": "^3.11.0",
- "winston-daily-rotate-file": "^4.7.1"
+ "winston-daily-rotate-file": "^4.7.1",
+ "ws": "^8.18.0"
},
"devDependencies": {
"daisyui": "^4.5.0",
@@ -433,9 +434,9 @@
"license": "MIT"
},
"node_modules/@types/cors": {
- "version": "2.8.17",
- "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
- "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
+ "version": "2.8.19",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+ "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
@@ -454,12 +455,12 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "22.10.2",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
- "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
+ "version": "24.0.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz",
+ "integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==",
"license": "MIT",
"dependencies": {
- "undici-types": "~6.20.0"
+ "undici-types": "~7.8.0"
}
},
"node_modules/@types/triple-beam": {
@@ -521,6 +522,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/activedirectory2/-/activedirectory2-2.2.0.tgz",
"integrity": "sha512-uGbw74xttFG6hgocU8T1a0oDofLsyTp44BPTn42JN5C2QlyO5kRl2E7ZoUdfpFzV+yxhaQTKI+8QqRB5HONYvA==",
+ "deprecated": "Decomissioned.",
"license": "MIT",
"dependencies": {
"abstract-logging": "^2.0.0",
@@ -797,12 +799,12 @@
}
},
"node_modules/assert-options": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.8.2.tgz",
- "integrity": "sha512-XaXoMxY0zuwAb0YuZjxIm8FeWvNq0aWNIbrzHhFjme8Smxw4JlPoyrAKQ6808k5UvQdhvnWqHZCphq5mXd4TDA==",
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.8.3.tgz",
+ "integrity": "sha512-s6v4HnA+vYSGO4eZX+F+I3gvF74wPk+m6Z1Q3w1Dsg4Pnv/R24vhKAasoMVZGvDpOOfTg1Qz4ptZnEbuy95XsQ==",
"license": "MIT",
"engines": {
- "node": ">=10.0.0"
+ "node": ">=14.0.0"
}
},
"node_modules/assert-plus": {
@@ -836,9 +838,9 @@
}
},
"node_modules/axios": {
- "version": "1.7.9",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
- "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
+ "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -1021,9 +1023,9 @@
"license": "MIT"
},
"node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -1119,28 +1121,10 @@
"node": ">=14.16"
}
},
- "node_modules/call-bind": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
- "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.0",
- "es-define-property": "^1.0.0",
- "get-intrinsic": "^1.2.4",
- "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==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -1151,13 +1135,13 @@
}
},
"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==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.8",
- "get-intrinsic": "^1.2.5"
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
@@ -1351,9 +1335,9 @@
}
},
"node_modules/compression": {
- "version": "1.7.5",
- "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz",
- "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==",
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz",
+ "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
@@ -1543,9 +1527,9 @@
}
},
"node_modules/daisyui": {
- "version": "4.12.22",
- "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.22.tgz",
- "integrity": "sha512-HDLWbmTnXxhE1MrMgSWjVgdRt+bVYHvfNbW3GTsyIokRSqTHonUTrxV3RhpPDjGIWaHt+ELtDCTYCtUFgL2/Nw==",
+ "version": "4.12.24",
+ "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.24.tgz",
+ "integrity": "sha512-JYg9fhQHOfXyLadrBrEqCDM6D5dWCSSiM6eTNCRrBRzx/VlOCrLS8eDfIw9RVvs64v2mJdLooKXY8EwQzoszAA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1575,9 +1559,9 @@
}
},
"node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -1627,23 +1611,6 @@
"node": ">=10"
}
},
- "node_modules/define-data-property": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
- "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0",
- "es-errors": "^1.3.0",
- "gopd": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -1688,9 +1655,9 @@
}
},
"node_modules/detect-libc": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
- "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -1754,9 +1721,9 @@
}
},
"node_modules/dotenv": {
- "version": "16.4.7",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
- "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
+ "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@@ -1780,12 +1747,12 @@
}
},
"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==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
- "call-bind-apply-helpers": "^1.0.0",
+ "call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
@@ -1851,12 +1818,11 @@
}
},
"node_modules/engine.io": {
- "version": "6.6.2",
- "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
- "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==",
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
+ "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
"license": "MIT",
"dependencies": {
- "@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
@@ -1880,12 +1846,6 @@
"node": ">=10.0.0"
}
},
- "node_modules/engine.io/node_modules/@types/cookie": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
- "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
- "license": "MIT"
- },
"node_modules/engine.io/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
@@ -1912,6 +1872,27 @@
}
}
},
+ "node_modules/engine.io/node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
@@ -1940,9 +1921,9 @@
}
},
"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==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -1951,6 +1932,21 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -2052,9 +2048,9 @@
"license": "MIT"
},
"node_modules/express-rate-limit": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz",
- "integrity": "sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
+ "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
"license": "MIT",
"engines": {
"node": ">= 16"
@@ -2063,7 +2059,7 @@
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
- "express": "4 || 5 || ^5.0.0-beta.1"
+ "express": "^4.11 || 5 || ^5.0.0-beta.1"
}
},
"node_modules/express-session": {
@@ -2149,16 +2145,16 @@
"license": "MIT"
},
"node_modules/fast-glob": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
- "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
- "micromatch": "^4.0.4"
+ "micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
@@ -2172,9 +2168,9 @@
"license": "MIT"
},
"node_modules/fastq": {
- "version": "1.17.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
- "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
@@ -2205,9 +2201,9 @@
}
},
"node_modules/filelist/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -2297,12 +2293,12 @@
}
},
"node_modules/foreground-child": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
- "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
- "cross-spawn": "^7.0.0",
+ "cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
@@ -2325,13 +2321,15 @@
}
},
"node_modules/form-data": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
- "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
+ "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -2372,9 +2370,9 @@
"license": "ISC"
},
"node_modules/fs-extra": {
- "version": "11.2.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
- "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
+ "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
@@ -2491,21 +2489,21 @@
}
},
"node_modules/get-intrinsic": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
- "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "dunder-proto": "^1.0.0",
+ "call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
+ "es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
- "math-intrinsics": "^1.0.0"
+ "math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
@@ -2514,6 +2512,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -2632,18 +2643,6 @@
"node": ">=8"
}
},
- "node_modules/has-property-descriptors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -2656,6 +2655,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
@@ -2675,9 +2689,9 @@
}
},
"node_modules/http-cache-semantics": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
- "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
+ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
"license": "BSD-2-Clause"
},
"node_modules/http-errors": {
@@ -2816,9 +2830,9 @@
}
},
"node_modules/is-core-module": {
- "version": "2.16.0",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz",
- "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==",
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@@ -2957,18 +2971,18 @@
}
},
"node_modules/jiti": {
- "version": "1.21.6",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
- "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
}
},
"node_modules/jose": {
- "version": "5.9.6",
- "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz",
- "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==",
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
+ "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
@@ -3027,12 +3041,12 @@
}
},
"node_modules/jwa": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
- "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+ "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
"license": "MIT",
"dependencies": {
- "buffer-equal-constant-time": "1.0.1",
+ "buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
@@ -3116,6 +3130,7 @@
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
+ "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
"license": "MIT"
},
"node_modules/lodash.includes": {
@@ -3134,6 +3149,7 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+ "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
@@ -3196,9 +3212,9 @@
}
},
"node_modules/long": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
- "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/lowercase-keys": {
@@ -3223,9 +3239,9 @@
}
},
"node_modules/lru.min": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz",
- "integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
+ "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
"license": "MIT",
"engines": {
"bun": ">=1.0.0",
@@ -3262,9 +3278,9 @@
}
},
"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==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -3344,9 +3360,9 @@
}
},
"node_modules/mime-db": {
- "version": "1.53.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
- "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -3468,9 +3484,10 @@
"license": "MIT"
},
"node_modules/multer": {
- "version": "1.4.5-lts.1",
- "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
- "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
+ "version": "1.4.5-lts.2",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
+ "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
+ "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.",
"license": "MIT",
"dependencies": {
"append-field": "^1.0.0",
@@ -3581,9 +3598,9 @@
}
},
"node_modules/mysql2": {
- "version": "3.11.5",
- "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.5.tgz",
- "integrity": "sha512-0XFu8rUmFN9vC0ME36iBvCUObftiMHItrYFhlCRvFWbLgpNqtC4Br/NmZX1HNCszxT0GGy5QtP+k3Q3eCJPaYA==",
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.1.tgz",
+ "integrity": "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==",
"license": "MIT",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
@@ -3636,16 +3653,16 @@
}
},
"node_modules/nan": {
- "version": "2.22.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz",
- "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==",
+ "version": "2.22.2",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz",
+ "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==",
"license": "MIT",
"optional": true
},
"node_modules/nanoid": {
- "version": "3.3.8",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
- "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
@@ -3726,18 +3743,18 @@
}
},
"node_modules/nodemailer": {
- "version": "6.9.16",
- "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz",
- "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==",
+ "version": "6.10.1",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz",
+ "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nodemon": {
- "version": "3.1.9",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
- "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==",
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
+ "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3811,9 +3828,9 @@
}
},
"node_modules/normalize-url": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz",
- "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.2.tgz",
+ "integrity": "sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==",
"license": "MIT",
"engines": {
"node": ">=14.16"
@@ -3836,9 +3853,9 @@
}
},
"node_modules/oauth": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.0.tgz",
- "integrity": "sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==",
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz",
+ "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==",
"license": "MIT"
},
"node_modules/oauth4webapi": {
@@ -3869,9 +3886,9 @@
}
},
"node_modules/object-inspect": {
- "version": "1.13.3",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
- "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -4127,14 +4144,109 @@
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"node_modules/pg": {
- "version": "8.13.1",
- "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz",
- "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz",
+ "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-connection-string": "^2.9.0",
+ "pg-pool": "^3.10.0",
+ "pg-protocol": "^1.10.0",
+ "pg-types": "2.2.0",
+ "pgpass": "1.0.5"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "optionalDependencies": {
+ "pg-cloudflare": "^1.2.5"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pg-cloudflare": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz",
+ "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.0.tgz",
+ "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==",
+ "license": "MIT"
+ },
+ "node_modules/pg-cursor": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/pg-cursor/-/pg-cursor-2.15.0.tgz",
+ "integrity": "sha512-sO3SQP9seXoaV7sddeKrPQk3zhnrMchCr71cYjkyNHCC6n3mA5AAyhK5foxy+yBtnBZuPqxzoiVxz3jKyV7D3g==",
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "pg": "^8"
+ }
+ },
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-minify": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.7.0.tgz",
+ "integrity": "sha512-kFPxAWAhPMvOqnY7klP3scdU5R7bxpAYOm8vGExuIkcSIwuFkZYl4C4XIPQ8DtXY2NzVmAX1aFHpvFSXQ/qQmA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.0.tgz",
+ "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-promise": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.14.0.tgz",
+ "integrity": "sha512-x/HZ6hK0MxYllyfUbmN/XZc7JBYoow7KElyNW9hnlhgRHMiRZmRUtfNM/wcuElpjSoASPxkoIKi4IA5QlwOONA==",
+ "license": "MIT",
+ "dependencies": {
+ "assert-options": "0.8.3",
+ "pg": "8.14.1",
+ "pg-minify": "1.7.0",
+ "spex": "3.4.1"
+ },
+ "engines": {
+ "node": ">=14.0"
+ },
+ "peerDependencies": {
+ "pg-query-stream": "4.8.1"
+ }
+ },
+ "node_modules/pg-promise/node_modules/pg": {
+ "version": "8.14.1",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.14.1.tgz",
+ "integrity": "sha512-0TdbqfjwIun9Fm/r89oB7RFQ0bLgduAhiIqIXOsyKoiC/L54DbuAAzIEN/9Op0f1Po9X7iCPXGoa/Ah+2aI8Xw==",
"license": "MIT",
"dependencies": {
"pg-connection-string": "^2.7.0",
- "pg-pool": "^3.7.0",
- "pg-protocol": "^1.7.0",
+ "pg-pool": "^3.8.0",
+ "pg-protocol": "^1.8.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
},
@@ -4153,88 +4265,20 @@
}
}
},
- "node_modules/pg-cloudflare": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
- "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
- "license": "MIT",
- "optional": true
- },
- "node_modules/pg-connection-string": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
- "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==",
- "license": "MIT"
- },
- "node_modules/pg-cursor": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/pg-cursor/-/pg-cursor-2.12.1.tgz",
- "integrity": "sha512-V13tEaA9Oq1w+V6Q3UBIB/blxJrwbbr35/dY54r/86soBJ7xkP236bXaORUTVXUPt9B6Ql2BQu+uwQiuMfRVgg==",
- "license": "MIT",
- "peer": true,
- "peerDependencies": {
- "pg": "^8"
- }
- },
- "node_modules/pg-int8": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
- "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
- "license": "ISC",
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/pg-minify": {
- "version": "1.6.5",
- "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.6.5.tgz",
- "integrity": "sha512-u0UE8veaCnMfJmoklqneeBBopOAPG3/6DHqGVHYAhz8DkJXh9dnjPlz25fRxn4e+6XVzdOp7kau63Rp52fZ3WQ==",
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/pg-pool": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz",
- "integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==",
- "license": "MIT",
- "peerDependencies": {
- "pg": ">=8.0"
- }
- },
- "node_modules/pg-promise": {
- "version": "11.10.2",
- "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.10.2.tgz",
- "integrity": "sha512-wK4yjxZdfxBmAMcs40q6IsC1SOzdLilc1yNvJqlbOjtm2syayqLDCt1JQ9lhS6yNSgVlGOQZT88yb/SADJmEBw==",
- "license": "MIT",
- "dependencies": {
- "assert-options": "0.8.2",
- "pg": "8.13.1",
- "pg-minify": "1.6.5",
- "spex": "3.4.0"
- },
- "engines": {
- "node": ">=14.0"
- },
- "peerDependencies": {
- "pg-query-stream": "4.7.1"
- }
- },
"node_modules/pg-protocol": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz",
- "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz",
+ "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==",
"license": "MIT"
},
"node_modules/pg-query-stream": {
- "version": "4.7.1",
- "resolved": "https://registry.npmjs.org/pg-query-stream/-/pg-query-stream-4.7.1.tgz",
- "integrity": "sha512-UMgsgn/pOIYsIifRySp59vwlpTpLADMK9HWJtq5ff0Z3MxBnPMGnCQeaQl5VuL+7ov4F96mSzIRIcz+Duo6OiQ==",
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/pg-query-stream/-/pg-query-stream-4.8.1.tgz",
+ "integrity": "sha512-kZo6C6HSzYFF6mlwl+etDk5QZD9CMdlHUXpof6PkK9+CHHaBLvOd2lZMwErOOpC/ldg4thrAojS8sG1B8PZ9Yw==",
"license": "MIT",
"peer": true,
"dependencies": {
- "pg-cursor": "^2.12.1"
+ "pg-cursor": "^2.13.1"
},
"peerDependencies": {
"pg": "^8"
@@ -4293,18 +4337,18 @@
}
},
"node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/postcss": {
- "version": "8.4.49",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
- "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "version": "8.5.5",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz",
+ "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==",
"funding": [
{
"type": "opencollective",
@@ -4321,7 +4365,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.7",
+ "nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -4401,15 +4445,15 @@
}
},
"node_modules/postcss-load-config/node_modules/yaml": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
- "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
+ "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
- "node": ">= 14"
+ "node": ">= 14.6"
}
},
"node_modules/postcss-nested": {
@@ -4722,9 +4766,9 @@
}
},
"node_modules/resolve": {
- "version": "1.22.9",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz",
- "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==",
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.0",
@@ -4734,6 +4778,9 @@
"bin": {
"resolve": "bin/resolve"
},
+ "engines": {
+ "node": ">= 0.4"
+ },
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -4760,9 +4807,9 @@
}
},
"node_modules/reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
@@ -4851,9 +4898,9 @@
"license": "MIT"
},
"node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -4936,23 +4983,6 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
- "node_modules/set-function-length": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
- "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.4",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -5134,6 +5164,27 @@
}
}
},
+ "node_modules/socket.io-adapter/node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
@@ -5191,9 +5242,9 @@
}
},
"node_modules/spex": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/spex/-/spex-3.4.0.tgz",
- "integrity": "sha512-8JeZJ7QlEBnSj1W1fKXgbB2KUPA8k4BxFMf6lZX/c1ZagU/1b9uZWZK0yD6yjfzqAIuTNG4YlRmtMpQiXuohsg==",
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/spex/-/spex-3.4.1.tgz",
+ "integrity": "sha512-Br0Mu3S+c70kr4keXF+6K4B8ohR+aJjI9s7SbdsI3hliE1Riz4z+FQk7FQL+r7X1t90KPkpuKwQyITpCIQN9mg==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -5335,9 +5386,9 @@
}
},
"node_modules/sucrase/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -5474,9 +5525,9 @@
}
},
"node_modules/swagger-ui-dist": {
- "version": "5.18.2",
- "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz",
- "integrity": "sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==",
+ "version": "5.24.1",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.24.1.tgz",
+ "integrity": "sha512-ITeWc7CCAfK53u8jnV39UNqStQZjSt+bVYtJHsOEL3vVj/WV9/8HmsF8Ej4oD8r+Xk1HpWyeW/t59r1QNeAcUQ==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
@@ -5498,9 +5549,9 @@
}
},
"node_modules/sweetalert2": {
- "version": "11.15.0",
- "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.15.0.tgz",
- "integrity": "sha512-34Xs0CFBac6I1cGG9d+XaBqJrp0F/0prr8rMYOcU0shU/XmkGkRtlCxWNi7PdKYGw9Qf6aoEHNYicX3au37nkw==",
+ "version": "11.22.0",
+ "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.22.0.tgz",
+ "integrity": "sha512-pSMuRGDULhh+wrFkO22O0YsIXxs8yFE0O+WVYXcqc/sTa1oRnf0JlR+vfQIRY1QM1UeFfnCjyw6DYnG75/oxiQ==",
"license": "MIT",
"funding": {
"type": "individual",
@@ -5508,9 +5559,9 @@
}
},
"node_modules/systeminformation": {
- "version": "5.23.13",
- "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.23.13.tgz",
- "integrity": "sha512-4Cn39sTXp7eN9rV/60aNdCXgpQ5xPcUUPIbwJjqTKj4/bNcSYtgZvFdXvZj6pRHP4h17uk6MfdMP5Fm6AK/b0Q==",
+ "version": "5.27.1",
+ "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.1.tgz",
+ "integrity": "sha512-FgkVpT6GgATtNvADgtEzDxI/SVaBisfnQ4fmgQZhCJ4335noTgt9q6O81ioHwzs9HgnJaaFSdHSEMIkneZ55iA==",
"license": "MIT",
"os": [
"darwin",
@@ -5534,9 +5585,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "3.4.16",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz",
- "integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==",
+ "version": "3.4.17",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
+ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@@ -5747,9 +5798,9 @@
"license": "MIT"
},
"node_modules/undici-types": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
+ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"license": "MIT"
},
"node_modules/universalify": {
@@ -5810,9 +5861,9 @@
}
},
"node_modules/validator": {
- "version": "13.12.0",
- "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
- "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
+ "version": "13.15.15",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
+ "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
@@ -6114,9 +6165,9 @@
"license": "ISC"
},
"node_modules/ws": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
- "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "version": "8.18.2",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
+ "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
diff --git a/package.json b/package.json
index 8ce3f13..5e70a4e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@cdn-app/insider-myaxrin-labs-dinawo",
- "version": "1.1.0-beta.1",
+ "version": "1.1.1-beta.1",
"description": "",
"main": "server.js",
"scripts": {
@@ -63,7 +63,8 @@
"tailwindcss": "^3.3.5",
"toastify-js": "^1.12.0",
"winston": "^3.11.0",
- "winston-daily-rotate-file": "^4.7.1"
+ "winston-daily-rotate-file": "^4.7.1",
+ "ws": "^8.18.0"
},
"devDependencies": {
"daisyui": "^4.5.0",
diff --git a/public/assets/homelab_logo@2x.png b/public/assets/homelab_logo@2x.png
deleted file mode 100644
index 0dadd90..0000000
Binary files a/public/assets/homelab_logo@2x.png and /dev/null differ
diff --git a/public/assets/homelab_logo@3x.png b/public/assets/homelab_logo@3x.png
deleted file mode 100644
index 0d2e462..0000000
Binary files a/public/assets/homelab_logo@3x.png and /dev/null differ
diff --git a/public/css/dashboard.styles.css b/public/css/dashboard.styles.css
index 631df81..5888c41 100644
--- a/public/css/dashboard.styles.css
+++ b/public/css/dashboard.styles.css
@@ -638,4 +638,1615 @@ position: relative !important;
.notification {
max-width: 100%;
}
+}
+
+.collaboration-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 4px 8px;
+ border-radius: 12px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.collaboration-badge.badge-info {
+ background-color: #17a2b8;
+ color: white;
+}
+
+.collaboration-badge.badge-secondary {
+ background-color: #6c757d;
+ color: white;
+}
+
+.toggle-collaboration-btn.active {
+ background-color: #17a2b8;
+ border-color: #17a2b8;
+ color: white;
+}
+
+.toggle-collaboration-btn:not(.active) {
+ background-color: #6c757d;
+ border-color: #6c757d;
+}
+
+.collaboration-details {
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+.user-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px;
+ border-bottom: 1px solid #eee;
+}
+
+.user-avatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+/* Styles pour le menu contextuel */
+.context-menu {
+ position: fixed;
+ z-index: 1000;
+ background-color: hsl(var(--card));
+ border: 1px solid hsl(var(--border));
+ border-radius: var(--radius);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+ min-width: 180px;
+ overflow: hidden; /* Pour que les hovers aillent jusqu'au bout */
+}
+
+/* Style pour les items du menu */
+.menu-item,
+.dropdown-item {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ padding: 0.5rem 1rem;
+ border: none;
+ background: none;
+ color: hsl(var(--foreground));
+ font-size: 0.875rem;
+ text-align: left;
+ cursor: pointer;
+ transition: background-color 0.1s ease;
+ text-decoration: none; /* Pour les liens */
+ white-space: nowrap;
+}
+
+/* Hover pour mode clair et sombre */
+.menu-item:hover,
+.dropdown-item:hover {
+ background-color: hsl(var(--accent));
+}
+
+.dark .menu-item:hover,
+.dark .dropdown-item:hover {
+ background-color: hsl(var(--accent));
+ color: hsl(var(--accent-foreground));
+}
+
+/* Alignement des icônes */
+.menu-item i,
+.dropdown-item i {
+ width: 20px;
+ margin-right: 0.75rem;
+ text-align: center;
+}
+
+/* Style pour les items destructifs (suppression) */
+.menu-item.destructive,
+.dropdown-item.destructive {
+ color: hsl(var(--destructive));
+}
+
+.menu-item.destructive:hover,
+.dropdown-item.destructive:hover {
+ background-color: hsl(var(--destructive));
+ color: hsl(var(--destructive-foreground));
+}
+
+/* Style pour le séparateur */
+.menu-separator,
+.dropdown-divider {
+ height: 1px;
+ background-color: hsl(var(--border));
+ margin: 0;
+}
+
+/* Menu déroulant */
+.dropdown-menu {
+ background-color: hsl(var(--card));
+ border: 1px solid hsl(var(--border));
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+ border-radius: var(--radius);
+ overflow: hidden;
+ min-width: 180px;
+}
+
+/* Animation */
+@keyframes menuFadeIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+.context-menu,
+.dropdown-menu.show {
+ animation: menuFadeIn 0.1s ease-out;
+}
+
+/* Dark mode hover fix */
+.dark .table tr:hover {
+ background-color: hsl(var(--muted)) !important;
+}
+
+/* Dropdown animation fix */
+.dropdown-menu {
+ display: none;
+ opacity: 0;
+ transform: translateY(-10px);
+ transition: opacity 0.15s ease-out, transform 0.15s ease-out;
+}
+
+.dropdown-menu.show {
+ display: block;
+ opacity: 1;
+ transform: translateY(0);
+}
+
+/* Double-click styles */
+tr[data-type="folder"], tr[data-type="shared-folder"] {
+ cursor: pointer;
+}
+
+tr[data-type="folder"]:active, tr[data-type="shared-folder"]:active {
+ background-color: hsl(var(--accent));
+}
+
+/* === VUE GRILLE REDESIGNÉE === */
+.grid-view {
+ display: grid !important;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 1.25rem;
+ padding: 1.5rem;
+ width: 100%;
+ max-width: 100%;
+ box-sizing: border-box;
+ animation: gridFadeIn 0.3s ease-out;
+ justify-content: start;
+ align-content: start;
+}
+
+.grid-view > tr {
+ display: block;
+ position: relative;
+ background: hsl(var(--card));
+ border: 1px solid hsl(var(--border));
+ border-radius: 12px;
+ padding: 0;
+ text-align: center;
+ cursor: pointer;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ height: auto;
+ overflow: hidden;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ max-width: 250px;
+ min-width: 180px;
+}
+
+.grid-view tr:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+ border-color: hsl(var(--primary) / 0.3);
+}
+
+.grid-view td {
+ display: block;
+ padding: 0 !important;
+ border: none !important;
+}
+
+.grid-view td:not(:first-child) {
+ display: none;
+}
+
+.grid-view .icon-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 1.5rem 1rem 1rem;
+ position: relative;
+}
+
+.grid-view .icon {
+ width: 56px;
+ height: 56px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, hsl(var(--primary) / 0.1), hsl(var(--primary) / 0.05));
+ border-radius: 12px;
+ margin-bottom: 0.5rem;
+ transition: transform 0.2s ease;
+}
+
+.grid-view tr:hover .icon {
+ transform: scale(1.1);
+}
+
+.grid-view .icon i {
+ font-size: 1.75rem;
+ color: hsl(var(--primary));
+}
+
+.grid-view .icon i.fa-folder {
+ color: #f59e0b;
+}
+
+.grid-view .icon i.fa-file {
+ color: #3b82f6;
+}
+
+.grid-view .icon i.fa-image {
+ color: #10b981;
+}
+
+.grid-view .icon i.fa-film {
+ color: #ef4444;
+}
+
+.grid-view .icon i.fa-file-pdf {
+ color: #dc2626;
+}
+
+.grid-view .label {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: hsl(var(--foreground));
+ margin: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ -webkit-box-orient: vertical;
+ max-width: 100%;
+ line-height: 1.3;
+ min-height: 2.6rem;
+}
+
+.grid-view .details {
+ font-size: 0.75rem;
+ color: hsl(var(--muted-foreground));
+ margin-top: 0.25rem;
+ padding: 0.5rem 0;
+ border-top: 1px solid hsl(var(--border) / 0.5);
+ width: 100%;
+}
+
+/* Actions pour la vue grille */
+.grid-view .grid-dropdown {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.5rem;
+ opacity: 0;
+ transition: all 0.2s ease;
+ z-index: 10;
+}
+
+.grid-view tr:hover .grid-dropdown {
+ opacity: 1;
+}
+
+.grid-view .grid-dropdown .dropdown-toggle {
+ background: rgba(255, 255, 255, 0.9);
+ border: 1px solid hsl(var(--border));
+ border-radius: 6px;
+ padding: 0.25rem 0.5rem;
+ font-size: 0.75rem;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.grid-view .grid-dropdown .dropdown-menu {
+ font-size: 0.85rem;
+ min-width: 140px;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+/* Badge de collaboration pour la vue grille */
+.grid-view .collaboration-badge {
+ position: absolute;
+ top: 0.75rem;
+ left: 0.75rem;
+ font-size: 0.65rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: 8px;
+ background: hsl(var(--primary));
+ color: hsl(var(--primary-foreground));
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+/* Styles améliorés pour la vue liste */
+.table:not(.grid-view) tr {
+ transition: background-color 0.2s ease;
+}
+
+.table:not(.grid-view) td {
+ vertical-align: middle;
+ padding: 1rem 0.75rem;
+}
+
+.table:not(.grid-view) td:first-child {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.table:not(.grid-view) .icon i {
+ font-size: 1.2rem;
+ width: 24px;
+ text-align: center;
+}
+
+/* Bouton de basculement vue grille/liste */
+.view-toggle-btn {
+ background: hsl(var(--secondary));
+ color: hsl(var(--secondary-foreground));
+ border: 1px solid hsl(var(--border));
+ padding: 0.5rem;
+ border-radius: var(--radius);
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.view-toggle-btn:hover {
+ background: hsl(var(--accent));
+ color: hsl(var(--accent-foreground));
+}
+
+.view-toggle-btn i {
+ font-size: 1.1rem;
+}
+
+/* Animation de transition entre les vues */
+.table tbody {
+ transition: all 0.3s ease;
+}
+
+/* Animations pour la vue grille */
+@keyframes gridFadeIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes itemSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.grid-view tr {
+ animation: itemSlideIn 0.3s ease-out backwards;
+}
+
+.grid-view tr:nth-child(1) { animation-delay: 0.05s; }
+.grid-view tr:nth-child(2) { animation-delay: 0.1s; }
+.grid-view tr:nth-child(3) { animation-delay: 0.15s; }
+.grid-view tr:nth-child(4) { animation-delay: 0.2s; }
+.grid-view tr:nth-child(5) { animation-delay: 0.25s; }
+.grid-view tr:nth-child(6) { animation-delay: 0.3s; }
+
+/* Responsive adjustments */
+@media (max-width: 768px) {
+ .grid-view {
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+ gap: 1rem;
+ padding: 1rem;
+ justify-content: center;
+ }
+
+ .grid-view .icon {
+ width: 48px;
+ height: 48px;
+ }
+
+ .grid-view .icon i {
+ font-size: 1.5rem;
+ }
+
+ .grid-view .label {
+ font-size: 0.85rem;
+ }
+
+ .grid-view .details {
+ font-size: 0.7rem;
+ }
+}
+
+@media (max-width: 480px) {
+ .grid-view {
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: 0.75rem;
+ padding: 0.75rem;
+ justify-content: center;
+ }
+
+ .grid-view .icon-container {
+ padding: 1rem 0.5rem 0.75rem;
+ }
+}
+
+/* Conteneur du tableau pour éviter l'espace vide */
+#fileTable {
+ width: 100%;
+ table-layout: auto;
+}
+
+.grid-view.table {
+ table-layout: unset;
+ width: auto;
+ max-width: 100%;
+}
+
+/* Dark mode adjustments */
+.dark .grid-view tr {
+ background: hsl(var(--card));
+ border-color: hsl(var(--border));
+}
+
+.dark .grid-view tr:hover {
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
+}
+
+.dark .grid-view .grid-dropdown .dropdown-toggle {
+ background: rgba(0, 0, 0, 0.8);
+ color: hsl(var(--foreground));
+}
+
+/* Correction pour éviter l'espace vide à droite */
+.table.grid-view {
+ display: grid !important;
+ table-layout: unset;
+ border-collapse: unset;
+}
+
+.table-responsive .grid-view {
+ overflow: visible;
+}
+
+.grid-view tbody {
+ display: contents;
+}
+
+.grid-view thead,
+.grid-view tbody tr:empty {
+ display: none;
+}
+
+@keyframes fadeScale {
+ from {
+ opacity: 0;
+ transform: scale(0.98);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+.grid-view {
+ animation: fadeScale 0.2s ease-out;
+}
+
+/* Modal Collaboration - Design moderne */
+.collaboration-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(8px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1050;
+ opacity: 0;
+ visibility: hidden;
+ transition: all 0.3s ease;
+}
+
+.collaboration-modal.show {
+ opacity: 1;
+ visibility: visible;
+}
+
+.collaboration-modal .modal-content {
+ background: hsl(var(--card));
+ border: 1px solid hsl(var(--border));
+ border-radius: 16px;
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
+ max-width: 600px;
+ width: 90%;
+ max-height: 90vh;
+ overflow: hidden;
+ transform: scale(0.9) translateY(20px);
+ transition: transform 0.3s ease;
+}
+
+.collaboration-modal.show .modal-content {
+ transform: scale(1) translateY(0);
+}
+
+.collaboration-modal .modal-header {
+ padding: 2rem 2rem 1rem 2rem;
+ border-bottom: 1px solid hsl(var(--border));
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.collaboration-modal .modal-title {
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: hsl(var(--foreground));
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.collaboration-modal .modal-title i {
+ color: hsl(var(--primary));
+}
+
+.collaboration-modal .close {
+ background: none;
+ border: none;
+ font-size: 1.5rem;
+ cursor: pointer;
+ color: hsl(var(--muted-foreground));
+ padding: 0.5rem;
+ border-radius: 8px;
+ transition: all 0.2s ease;
+}
+
+.collaboration-modal .close:hover {
+ background: hsl(var(--accent));
+ color: hsl(var(--accent-foreground));
+}
+
+.collaboration-modal .modal-body {
+ padding: 1.5rem 2rem;
+ max-height: 60vh;
+ overflow-y: auto;
+}
+
+.collaboration-modal .modal-footer {
+ padding: 1rem 2rem 2rem 2rem;
+ border-top: 1px solid hsl(var(--border));
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1rem;
+}
+
+/* Section des utilisateurs collaborateurs */
+.collaboration-users {
+ margin-bottom: 2rem;
+}
+
+.collaboration-users h6 {
+ font-size: 1rem;
+ font-weight: 600;
+ color: hsl(var(--foreground));
+ margin-bottom: 1rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.collaboration-user-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 1rem;
+ background: hsl(var(--muted) / 0.3);
+ border: 1px solid hsl(var(--border));
+ border-radius: 12px;
+ margin-bottom: 0.75rem;
+ transition: all 0.2s ease;
+}
+
+.collaboration-user-item:hover {
+ background: hsl(var(--muted) / 0.5);
+ transform: translateY(-1px);
+}
+
+.collaboration-user-info {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.collaboration-user-avatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ object-fit: cover;
+ border: 2px solid hsl(var(--border));
+}
+
+.collaboration-user-details h6 {
+ margin: 0;
+ font-size: 0.95rem;
+ font-weight: 500;
+ color: hsl(var(--foreground));
+}
+
+.collaboration-user-details small {
+ color: hsl(var(--muted-foreground));
+ font-size: 0.8rem;
+}
+
+.collaboration-user-status {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.status-indicator {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: hsl(var(--primary));
+ animation: pulse 2s infinite;
+}
+
+.status-indicator.offline {
+ background: hsl(var(--muted-foreground));
+ animation: none;
+}
+
+@keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+/* Section d'ajout de collaborateur */
+.add-collaborator {
+ padding: 1.5rem;
+ background: hsl(var(--accent) / 0.1);
+ border: 1px dashed hsl(var(--border));
+ border-radius: 12px;
+}
+
+.add-collaborator h6 {
+ font-size: 1rem;
+ font-weight: 600;
+ color: hsl(var(--foreground));
+ margin-bottom: 1rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.search-input-group {
+ position: relative;
+ margin-bottom: 1rem;
+}
+
+.search-input-group input {
+ width: 100%;
+ padding: 0.75rem 1rem 0.75rem 2.5rem;
+ border: 1px solid hsl(var(--border));
+ border-radius: 8px;
+ background: hsl(var(--background));
+ color: hsl(var(--foreground));
+ font-size: 0.9rem;
+ transition: all 0.2s ease;
+}
+
+.search-input-group input:focus {
+ outline: none;
+ border-color: hsl(var(--primary));
+ box-shadow: 0 0 0 3px hsl(var(--primary) / 0.1);
+}
+
+.search-input-group i {
+ position: absolute;
+ left: 0.75rem;
+ top: 50%;
+ transform: translateY(-50%);
+ color: hsl(var(--muted-foreground));
+ font-size: 0.9rem;
+}
+
+.search-results {
+ max-height: 200px;
+ overflow-y: auto;
+ border: 1px solid hsl(var(--border));
+ border-radius: 8px;
+ background: hsl(var(--background));
+ margin-top: 0.5rem;
+}
+
+.user-result {
+ padding: 0.75rem 1rem;
+ border-bottom: 1px solid hsl(var(--border));
+ transition: background 0.2s ease;
+}
+
+.user-result:last-child {
+ border-bottom: none;
+}
+
+.user-result:hover {
+ background: hsl(var(--accent) / 0.2);
+}
+
+.user-result .user-info {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.user-result .user-avatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ margin-right: 0.75rem;
+ object-fit: cover;
+}
+
+.user-result .user-name {
+ font-weight: 500;
+ color: hsl(var(--foreground));
+}
+
+.user-result .btn {
+ padding: 0.4rem 0.8rem;
+ font-size: 0.8rem;
+ border-radius: 6px;
+}
+
+/* État vide */
+.empty-state {
+ text-align: center;
+ padding: 2rem 1rem;
+ color: hsl(var(--muted-foreground));
+}
+
+.empty-state i {
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+ opacity: 0.5;
+}
+
+.empty-state p {
+ margin: 0;
+ font-size: 0.9rem;
+}
+
+/* Context Menu amélioré */
+.context-menu {
+ min-width: 220px;
+ padding: 0.5rem;
+}
+
+.context-menu .menu-item {
+ border-radius: var(--radius);
+ margin: 0.2rem 0;
+}
+
+/* Style du menu contextuel en mode sombre */
+.dark .context-menu {
+ background-color: hsl(var(--card));
+ border-color: hsl(var(--border));
+}
+
+.dark .context-menu .menu-item {
+ color: hsl(var(--foreground));
+}
+
+.dark .context-menu .menu-item:hover {
+ background-color: hsl(var(--accent));
+ color: hsl(var(--accent-foreground));
+}
+
+/* Correction des modales en mode sombre */
+.dark .modal-content {
+ background-color: hsl(var(--card));
+ color: hsl(var(--foreground));
+}
+
+.dark .modal-header {
+ border-bottom-color: hsl(var(--border));
+}
+
+.dark .modal-footer {
+ border-top-color: hsl(var(--border));
+}
+
+.dark .modal .close {
+ color: hsl(var(--foreground));
+}
+
+.dark .modal-body {
+ color: hsl(var(--foreground));
+}
+
+.dark .form-control {
+ background-color: hsl(var(--background));
+ border-color: hsl(var(--border));
+ color: hsl(var(--foreground));
+}
+
+/* SweetAlert2 Dark Mode */
+.dark .swal2-popup {
+ background-color: hsl(var(--card)) !important;
+ color: hsl(var(--foreground)) !important;
+}
+
+.dark .swal2-title {
+ color: hsl(var(--foreground)) !important;
+}
+
+.dark .swal2-content {
+ color: hsl(var(--foreground)) !important;
+}
+
+.dark .swal2-input {
+ background-color: hsl(var(--background)) !important;
+ border-color: hsl(var(--border)) !important;
+ color: hsl(var(--foreground)) !important;
+}
+
+/* Nouveau style pour le bouton de copie de lien */
+.copy-link-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ width: 100%;
+ padding: 0.5rem 1rem;
+ color: hsl(var(--foreground));
+ background: none;
+ border: none;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.copy-link-btn:hover {
+ background-color: hsl(var(--accent));
+ color: hsl(var(--accent-foreground));
+}
+
+.copy-link-btn i {
+ width: 20px;
+ text-align: center;
+}
+
+/* =================== MODAL COLLABORATION MODERNE =================== */
+.collaboration-modal {
+ display: none !important;
+ position: fixed !important;
+ top: 0 !important;
+ left: 0 !important;
+ width: 100% !important;
+ height: 100% !important;
+ z-index: 1050 !important;
+ background-color: rgba(0, 0, 0, 0.6) !important;
+ backdrop-filter: blur(12px) !important;
+ -webkit-backdrop-filter: blur(12px) !important;
+ animation: overlayShow 0.15s cubic-bezier(0.16, 1, 0.3, 1) !important;
+ justify-content: center !important;
+ align-items: center !important;
+}
+
+.collaboration-modal.show,
+.collaboration-modal[style*="display: block"] {
+ display: flex !important;
+}
+
+.collaboration-modal .modal-dialog {
+ position: relative !important;
+ width: 90vw !important;
+ max-width: 580px !important;
+ max-height: 85vh !important;
+ margin: 0 !important;
+ transform: none !important;
+}
+
+.collaboration-modal .modal-content {
+ background: hsl(var(--card)) !important;
+ border-radius: 16px !important;
+ border: 1px solid hsl(var(--border)) !important;
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.05) !important;
+ animation: contentShow 0.15s cubic-bezier(0.16, 1, 0.3, 1) !important;
+ overflow: hidden !important;
+ position: relative !important;
+ width: 100% !important;
+}
+
+.collaboration-modal .modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 20px 24px 16px;
+ border-bottom: 1px solid hsl(var(--border));
+ background: hsl(var(--card));
+}
+
+.collaboration-modal .modal-title-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.collaboration-modal .modal-icon {
+ width: 24px;
+ height: 24px;
+ color: hsl(var(--primary));
+ font-size: 20px;
+}
+
+.collaboration-modal .modal-title {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ color: hsl(var(--foreground));
+}
+
+.collaboration-modal .close {
+ background: none;
+ border: none;
+ color: hsl(var(--muted-foreground));
+ width: 32px;
+ height: 32px;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.15s ease;
+ cursor: pointer;
+ font-size: 16px;
+ padding: 0;
+}
+
+.collaboration-modal .close:hover {
+ background: hsl(var(--accent));
+ color: hsl(var(--accent-foreground));
+}
+
+.collaboration-modal .modal-body {
+ padding: 24px;
+ max-height: calc(85vh - 140px);
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+/* Sections */
+.collaboration-section {
+ background: hsl(var(--background));
+ border-radius: 12px;
+ border: 1px solid hsl(var(--border));
+ overflow: hidden;
+}
+
+.collaboration-section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 20px;
+ background: hsl(var(--muted) / 0.3);
+ border-bottom: 1px solid hsl(var(--border));
+}
+
+.section-title-group {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.section-icon {
+ width: 18px;
+ height: 18px;
+ color: hsl(var(--primary));
+ font-size: 16px;
+}
+
+.collaboration-section-title {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 600;
+ color: hsl(var(--foreground));
+}
+
+.collaboration-count-badge {
+ background: hsl(var(--primary));
+ color: hsl(var(--primary-foreground));
+ padding: 4px 8px;
+ border-radius: 12px;
+ font-size: 12px;
+ font-weight: 600;
+ min-width: 24px;
+ text-align: center;
+}
+
+/* Liste des utilisateurs */
+.collaboration-users-list {
+ padding: 16px 20px;
+}
+
+.collaboration-user-item {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ background: hsl(var(--card));
+ border: 1px solid hsl(var(--border));
+ border-radius: 12px;
+ margin-bottom: 12px;
+ transition: all 0.2s ease;
+}
+
+.collaboration-user-item:last-child {
+ margin-bottom: 0;
+}
+
+.collaboration-user-item:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+ border-color: hsl(var(--primary) / 0.5);
+}
+
+.user-avatar-wrapper {
+ position: relative;
+ flex-shrink: 0;
+}
+
+.user-avatar {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ object-fit: cover;
+ border: 2px solid hsl(var(--border));
+}
+
+.user-status-indicator {
+ position: absolute;
+ bottom: 2px;
+ right: 2px;
+ width: 14px;
+ height: 14px;
+ background: #10b981;
+ border: 2px solid hsl(var(--card));
+ border-radius: 50%;
+ animation: pulse 2s infinite;
+}
+
+.user-details {
+ flex: 1;
+ min-width: 0;
+}
+
+.user-name {
+ font-size: 16px;
+ font-weight: 600;
+ color: hsl(var(--foreground));
+ margin-bottom: 4px;
+}
+
+.user-role {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 13px;
+ color: hsl(var(--muted-foreground));
+}
+
+.user-role i {
+ font-size: 12px;
+ color: hsl(var(--primary));
+}
+
+.user-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.action-btn {
+ width: 36px;
+ height: 36px;
+ border: none;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ font-size: 14px;
+}
+
+.remove-btn {
+ background: rgba(239, 68, 68, 0.1);
+ color: #ef4444;
+}
+
+.remove-btn:hover {
+ background: #ef4444;
+ color: white;
+ transform: scale(1.05);
+}
+
+/* État vide */
+.collaboration-empty-state {
+ text-align: center;
+ padding: 40px 20px;
+ color: hsl(var(--muted-foreground));
+}
+
+.empty-icon {
+ font-size: 48px;
+ color: hsl(var(--muted-foreground));
+ margin-bottom: 16px;
+ opacity: 0.5;
+}
+
+.collaboration-empty-state p {
+ font-size: 16px;
+ font-weight: 500;
+ margin: 0 0 8px 0;
+ color: hsl(var(--foreground));
+}
+
+.collaboration-empty-state span {
+ font-size: 14px;
+ color: hsl(var(--muted-foreground));
+}
+
+/* Conteneur de recherche */
+.collaboration-search-container {
+ padding: 20px;
+}
+
+.search-input-group {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.search-input-wrapper {
+ position: relative;
+ flex: 1;
+}
+
+.search-icon {
+ position: absolute;
+ left: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: hsl(var(--muted-foreground));
+ font-size: 14px;
+}
+
+.search-input {
+ width: 100%;
+ padding: 12px 12px 12px 40px;
+ border: 1px solid hsl(var(--border));
+ border-radius: 8px;
+ background: hsl(var(--card));
+ color: hsl(var(--foreground));
+ font-size: 14px;
+ outline: none;
+ transition: all 0.15s ease;
+}
+
+.search-input:focus {
+ border-color: hsl(var(--primary));
+ box-shadow: 0 0 0 3px hsl(var(--primary) / 0.1);
+}
+
+.search-input::placeholder {
+ color: hsl(var(--muted-foreground));
+}
+
+.search-btn {
+ padding: 12px 20px;
+ border: none;
+ border-radius: 8px;
+ background: hsl(var(--primary));
+ color: hsl(var(--primary-foreground));
+ font-size: 14px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ white-space: nowrap;
+}
+
+.search-btn:hover {
+ background: hsl(var(--primary) / 0.9);
+ transform: translateY(-1px);
+}
+
+/* Résultats de recherche */
+.search-results {
+ min-height: 60px;
+}
+
+.search-loading,
+.search-no-result,
+.search-error {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+ padding: 24px;
+ background: hsl(var(--muted) / 0.3);
+ border-radius: 8px;
+ color: hsl(var(--muted-foreground));
+ font-size: 14px;
+}
+
+.search-loading i {
+ color: hsl(var(--primary));
+}
+
+.search-error {
+ color: #ef4444;
+}
+
+.search-result-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 16px;
+ background: hsl(var(--card));
+ border: 1px solid hsl(var(--border));
+ border-radius: 8px;
+ animation: slideInUp 0.3s ease;
+}
+
+.result-user-info {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex: 1;
+}
+
+.result-avatar-wrapper {
+ flex-shrink: 0;
+}
+
+.result-avatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ object-fit: cover;
+ border: 2px solid hsl(var(--border));
+}
+
+.result-details {
+ flex: 1;
+}
+
+.result-name {
+ font-size: 14px;
+ font-weight: 600;
+ color: hsl(var(--foreground));
+ margin-bottom: 2px;
+}
+
+.result-status {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: hsl(var(--muted-foreground));
+}
+
+.result-status i {
+ color: #10b981;
+ font-size: 11px;
+}
+
+.add-user-btn {
+ padding: 8px 16px;
+ border: none;
+ border-radius: 6px;
+ background: hsl(var(--primary));
+ color: hsl(var(--primary-foreground));
+ font-size: 13px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ white-space: nowrap;
+}
+
+.add-user-btn:hover {
+ background: hsl(var(--primary) / 0.9);
+ transform: translateY(-1px);
+}
+
+/* Footer et boutons */
+.collaboration-modal .modal-footer {
+ border-top: 1px solid hsl(var(--border));
+ padding: 16px 24px;
+ display: flex;
+ justify-content: flex-end;
+ gap: 12px;
+ background: hsl(var(--background) / 0.5);
+}
+
+.collaboration-modal .btn {
+ border-radius: 8px;
+ padding: 10px 16px;
+ font-weight: 500;
+ font-size: 14px;
+ transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.collaboration-modal .btn:hover {
+ transform: translateY(-1px);
+}
+
+.collaboration-modal .btn-danger {
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
+ color: white;
+}
+
+.collaboration-modal .btn-danger:hover {
+ background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
+ box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
+}
+
+.collaboration-modal .btn-secondary {
+ background: hsl(var(--secondary));
+ color: hsl(var(--secondary-foreground));
+ border: 1px solid hsl(var(--border));
+}
+
+.collaboration-modal .btn-secondary:hover {
+ background: hsl(var(--accent));
+ color: hsl(var(--accent-foreground));
+}
+
+/* Animations */
+@keyframes overlayShow {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes contentShow {
+ from {
+ opacity: 0;
+ transform: scale(0.96);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes slideInUp {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes pulse {
+ 0%, 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 0.6;
+ transform: scale(0.95);
+ }
+}
+
+/* Badge de collaboration amélioré */
+.collaboration-badge {
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+ color: white;
+ padding: 0.25rem 0.75rem;
+ border-radius: 20px;
+ font-size: 0.75rem;
+ font-weight: 500;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ transition: all 0.2s ease;
+ animation: fadeIn 0.3s ease;
+}
+
+.collaboration-badge.badge-secondary {
+ background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);
+}
+
+.collaboration-badge:hover {
+ transform: scale(1.05);
+}
+
+.active-users-count {
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 10px;
+ padding: 0.125rem 0.375rem;
+ font-size: 0.6875rem;
+ font-weight: 600;
+}
+
+/* Mode sombre */
+.dark .collaboration-modal .modal-content {
+ background: hsl(var(--card));
+ border-color: hsl(var(--border));
+ box-shadow:
+ 0 25px 50px -12px rgba(0, 0, 0, 0.5),
+ 0 0 0 1px rgba(255, 255, 255, 0.1);
+}
+
+.dark .collaboration-section {
+ background: hsl(var(--background));
+ border-color: hsl(var(--border));
+}
+
+.dark .collaboration-user-item {
+ background: hsl(var(--background));
+ border-color: hsl(var(--border));
+}
+
+.dark .search-input {
+ background: hsl(var(--background));
+ color: hsl(var(--foreground));
+ border-color: hsl(var(--border));
+}
+
+.dark .search-result-item {
+ background: hsl(var(--background));
+ border-color: hsl(var(--border));
+}
+
+/* Responsive */
+@media (max-width: 640px) {
+ .collaboration-modal .modal-dialog {
+ width: 95vw;
+ max-width: none;
+ margin: 8px;
+ }
+
+ .collaboration-modal .modal-header,
+ .collaboration-modal .modal-body {
+ padding-left: 16px;
+ padding-right: 16px;
+ }
+
+ .collaboration-modal .modal-footer {
+ padding: 12px 16px;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .collaboration-modal .btn {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .search-input-group {
+ flex-direction: column;
+ }
+
+ .search-btn {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .collaboration-user-item {
+ padding: 12px;
+ gap: 12px;
+ }
+
+ .user-avatar {
+ width: 40px;
+ height: 40px;
+ }
+
+ .search-result-item {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 12px;
+ }
+
+ .result-user-info {
+ justify-content: center;
+ }
+
+ .add-user-btn {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .user-item {
+ padding: 10px;
+ }
+
+ .user-avatar {
+ width: 36px;
+ height: 36px;
+ }
+}
+
+/* Badge de collaboration amélioré */
+.collaboration-badge {
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+ color: white;
+ padding: 0.25rem 0.75rem;
+ border-radius: 20px;
+ font-size: 0.75rem;
+ font-weight: 500;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ transition: all 0.2s ease;
+ animation: fadeIn 0.3s ease;
+}
+
+.collaboration-badge.badge-secondary {
+ background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);
+}
+
+.collaboration-badge:hover {
+ transform: scale(1.05);
+}
+
+.active-users-count {
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 10px;
+ padding: 0.125rem 0.375rem;
+ font-size: 0.6875rem;
+ font-weight: 600;
}
\ No newline at end of file
diff --git a/public/js/dashboard-old.js b/public/js/dashboard-old.js
new file mode 100644
index 0000000..b81ec16
--- /dev/null
+++ b/public/js/dashboard-old.js
@@ -0,0 +1,2169 @@
+document.addEventListener('DOMContentLoaded', function() {
+ document.querySelectorAll('.file-size').forEach(function(element) {
+ const size = parseInt(element.getAttribute('data-size'));
+ element.textContent = formatFileSize(size);
+ });
+
+ document.getElementById('searchButton').addEventListener('click', searchFiles);
+ document.getElementById('newFolderBtn').addEventListener('click', showNewFolderModal);
+
+ document.querySelectorAll('.delete-folder-button').forEach(button => {
+ button.addEventListener('click', function() {
+ const folderName = this.getAttribute('data-folder-name');
+ confirmDeleteFolder(folderName);
+ });
+ });
+
+ document.querySelectorAll('.delete-file-button').forEach(button => {
+ button.addEventListener('click', function() {
+ const fileName = this.getAttribute('data-file-name');
+ confirmDelete(fileName);
+ });
+ });
+
+ document.querySelectorAll('.copy-button').forEach(button => {
+ button.addEventListener('click', function() {
+ const fileUrl = this.getAttribute('data-file-url');
+ copyFileLink(fileUrl);
+ });
+ });
+
+ document.querySelectorAll('.rename-file-btn').forEach(button => {
+ button.addEventListener('click', function() {
+ const fileName = this.getAttribute('data-file-name');
+ const folderName = this.getAttribute('data-folder-name');
+ renameFile(folderName, fileName);
+ });
+ });
+
+ document.querySelectorAll('.move-file-btn').forEach(button => {
+ button.addEventListener('click', function() {
+ const fileName = this.getAttribute('data-file-name');
+ showMoveFileModal(fileName);
+ });
+ });
+
+ document.getElementById('confirmMoveFile').addEventListener('click', moveFile);
+ document.getElementById('themeSwitcher').addEventListener('click', toggleDarkMode);
+
+ initTheme();
+
+ document.addEventListener('DOMContentLoaded', function () {
+ const accountDropdownBtn = document.getElementById('accountDropdownBtn');
+ const accountDropdownMenu = document.getElementById('accountDropdownMenu');
+
+ accountDropdownBtn.addEventListener('click', function (e) {
+ e.stopPropagation();
+ accountDropdownMenu.classList.toggle('show');
+ });
+
+ document.addEventListener('click', function (e) {
+ if (!accountDropdownBtn.contains(e.target) && !accountDropdownMenu.contains(e.target)) {
+ accountDropdownMenu.classList.remove('show');
+ }
+ });
+ });
+
+ $('.modal').modal({
+ show: false
+ });
+
+ const metadataLink = document.querySelector('a[onclick="displayMetadata()"]');
+ if (metadataLink) {
+ metadataLink.addEventListener('click', function(event) {
+ event.preventDefault();
+ displayMetadata();
+ });
+ }
+
+ document.querySelectorAll('[onclick^="showFileInfo"]').forEach(link => {
+ link.addEventListener('click', function(event) {
+ event.preventDefault();
+ const fileName = this.getAttribute('onclick').match(/'([^']+)'/)[1];
+ showFileInfo(fileName);
+ });
+ });
+ });
+
+ function formatFileSize(fileSizeInBytes) {
+ if (fileSizeInBytes < 1024) return fileSizeInBytes + ' octets';
+ else if (fileSizeInBytes < 1048576) return (fileSizeInBytes / 1024).toFixed(2) + ' Ko';
+ else if (fileSizeInBytes < 1073741824) return (fileSizeInBytes / 1048576).toFixed(2) + ' Mo';
+ else return (fileSizeInBytes / 1073741824).toFixed(2) + ' Go';
+ }
+
+ function searchFiles() {
+ const input = document.getElementById('searchInput');
+ const filter = input.value.toUpperCase();
+ const table = document.getElementById('fileTable');
+ const tr = table.getElementsByTagName('tr');
+
+ for (let i = 1; i < tr.length; i++) {
+ const td = tr[i].getElementsByTagName('td')[0];
+ if (td) {
+ const txtValue = td.textContent || td.innerText;
+ if (txtValue.toUpperCase().indexOf(filter) > -1) {
+ tr[i].style.display = "";
+ } else {
+ tr[i].style.display = "none";
+ }
+ }
+ }
+ }
+
+ function showNewFolderModal() {
+ Swal.fire({
+ title: 'Nouveau dossier',
+ input: 'text',
+ inputPlaceholder: 'Entrer le nom du nouveau dossier',
+ confirmButtonText: 'Créer',
+ showCancelButton: true,
+ cancelButtonText: 'Annuler',
+ preConfirm: (folderName) => {
+ if (!folderName) {
+ Swal.showValidationMessage('Le nom du dossier ne peut pas être vide.');
+ }
+ return folderName;
+ }
+ }).then(result => {
+ if (result.isConfirmed) {
+ createNewFolder(result.value);
+ }
+ });
+ }
+
+ function createNewFolder(folderName) {
+ fetch('/api/dpanel/dashboard/newfolder', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ folderName }),
+ })
+ .then(response => {
+ if (response.ok) {
+ return response.json();
+ } else {
+ return response.json().then(error => Promise.reject(error));
+ }
+ })
+ .then(result => {
+ Swal.fire({
+ position: 'top',
+ icon: 'success',
+ title: 'Le dossier a été créé avec succès.',
+ showConfirmButton: false,
+ timer: 2000,
+ toast: true
+ }).then(() => {
+ location.reload();
+ });
+ })
+ .catch(error => {
+ Swal.fire({
+ position: 'top',
+ icon: 'error',
+ title: 'Erreur lors de la création du dossier.',
+ text: error.message,
+ showConfirmButton: false,
+ timer: 2350,
+ toast: true
+ });
+ });
+ }
+
+ function confirmDeleteFolder(folderName) {
+ Swal.fire({
+ title: 'Êtes-vous sûr?',
+ text: `La suppression du dossier "${folderName}" est irréversible!`,
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#d33',
+ cancelButtonColor: '#3085d6',
+ confirmButtonText: 'Supprimer',
+ cancelButtonText: 'Annuler',
+ }).then((result) => {
+ if (result.isConfirmed) {
+ deleteFolder(folderName);
+ }
+ });
+ }
+
+ function deleteFolder(folderName) {
+ fetch(`/api/dpanel/dashboard/deletefolder/${folderName}`, {
+ method: 'DELETE',
+ })
+ .then(response => {
+ if (response.ok) {
+ Swal.fire({
+ position: 'top',
+ icon: 'success',
+ title: 'Le dossier a été supprimé avec succès.',
+ showConfirmButton: false,
+ timer: 1800,
+ toast: true
+ }).then(() => {
+ location.reload();
+ });
+ } else {
+ throw new Error('La suppression du dossier a échoué');
+ }
+ })
+ .catch(error => {
+ Swal.fire({
+ position: 'top',
+ icon: 'error',
+ title: error.message,
+ showConfirmButton: false,
+ timer: 1800,
+ toast: true
+ });
+ });
+ }
+
+ function confirmDelete(filename) {
+ Swal.fire({
+ title: 'Êtes-vous sûr de vouloir supprimer ce fichier?',
+ text: 'Cette action est irréversible!',
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#d33',
+ cancelButtonColor: '#3085d6',
+ confirmButtonText: 'Supprimer'
+ }).then((result) => {
+ if (result.isConfirmed) {
+ deleteFile(filename);
+ }
+ });
+ }
+
+ function deleteFile(filename) {
+ fetch('/api/dpanel/dashboard/delete', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ filename: filename,
+ }),
+ })
+ .then(response => {
+ if (response.ok) {
+ Swal.fire({
+ position: 'top',
+ icon: 'success',
+ title: 'Le fichier a été supprimé avec succès.',
+ showConfirmButton: false,
+ timer: 1800,
+ toast: true
+ }).then(() => {
+ location.reload();
+ });
+ } else {
+ throw new Error('La suppression du fichier a échoué');
+ }
+ })
+ .catch(error => {
+ Swal.fire({
+ position: 'top',
+ icon: 'error',
+ title: error.message,
+ showConfirmButton: false,
+ timer: 1800,
+ toast: true
+ });
+ });
+ }
+
+ function copyFileLink(fileUrl) {
+ navigator.clipboard.writeText(fileUrl).then(() => {
+ Swal.fire({
+ position: 'top',
+ icon: 'success',
+ title: 'Lien copié !',
+ showConfirmButton: false,
+ timer: 1500,
+ toast: true
+ });
+ }, (err) => {
+ console.error('Erreur lors de la copie: ', err);
+ });
+ }
+
+ function renameFile(folderName, currentName) {
+ Swal.fire({
+ title: 'Entrez le nouveau nom',
+ input: 'text',
+ inputValue: currentName,
+ inputPlaceholder: 'Nouveau nom',
+ showCancelButton: true,
+ confirmButtonText: 'Renommer',
+ cancelButtonText: 'Annuler',
+ inputValidator: (value) => {
+ if (!value) {
+ return 'Vous devez entrer un nom de fichier';
+ }
+ }
+ }).then((result) => {
+ if (result.isConfirmed) {
+ const newName = result.value;
+ fetch(`/api/dpanel/dashboard/rename/${folderName}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ currentName: currentName, newName: newName }),
+ })
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ return response.json();
+ })
+ .then(data => {
+ Swal.fire({
+ position: 'top',
+ icon: 'success',
+ title: 'Le fichier a été renommé avec succès.',
+ showConfirmButton: false,
+ timer: 1800,
+ toast: true,
+ }).then(() => {
+ location.reload();
+ });
+ })
+ .catch((error) => {
+ Swal.fire({
+ position: 'top',
+ icon: 'error',
+ title: 'Erreur lors du renommage du fichier.',
+ text: error.message,
+ showConfirmButton: false,
+ timer: 1800,
+ toast: true,
+ });
+ });
+ }
+ });
+ }
+
+ function showMoveFileModal(fileName) {
+ // Récupérer les dossiers depuis le tableau
+ const folders = Array.from(document.querySelectorAll('tr[data-type="folder"]'))
+ .map(folderRow => ({
+ name: folderRow.dataset.name,
+ value: folderRow.dataset.name // On s'assure d'avoir une valeur correcte
+ }));
+
+ Swal.fire({
+ title: 'Déplacer le fichier',
+ html: `
+
+ `,
+ showCancelButton: true,
+ confirmButtonText: 'Déplacer',
+ cancelButtonText: 'Annuler',
+ preConfirm: () => {
+ const select = document.getElementById('moveFolderSelect');
+ const folderName = select.value;
+ if (!folderName) {
+ Swal.showValidationMessage('Veuillez sélectionner un dossier');
+ return false;
+ }
+ return folderName; // On retourne directement la valeur du dossier
+ }
+ }).then((result) => {
+ if (result.isConfirmed && result.value) {
+ moveFile(fileName, result.value);
+ }
+ });
+ }
+
+ function moveFile(fileName, folderName) {
+ // Log pour debug
+ console.log('Moving file:', { fileName, folderName });
+
+ fetch('/api/dpanel/dashboard/movefile', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ fileName: fileName,
+ folderName: folderName // Maintenant c'est une chaîne de caractères valide
+ }),
+ })
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ return response.json();
+ })
+ .then(data => {
+ if (data.message === "File moved successfully") {
+ Swal.fire({
+ position: 'top',
+ icon: 'success',
+ title: 'Le fichier a été déplacé avec succès.',
+ showConfirmButton: false,
+ timer: 1800,
+ toast: true,
+ }).then(() => {
+ location.reload();
+ });
+ } else {
+ throw new Error(data.error || 'Une erreur est survenue');
+ }
+ })
+ .catch((error) => {
+ Swal.fire({
+ position: 'top',
+ icon: 'error',
+ title: 'Erreur lors du déplacement du fichier.',
+ text: error.message,
+ showConfirmButton: false,
+ timer: 1800,
+ toast: true,
+ });
+ });
+ }
+
+ const body = document.body;
+ const themeSwitcher = document.getElementById('themeSwitcher');
+
+ 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');
+ }
+ });
+
+ async function showFileInfo(fileName) {
+ try {
+ const response = await fetch('/api/dpanel/dashboard/getmetadatafile/file_info', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ fileLink: fileName,
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ const fileInfo = data.find(file => file.fileName === fileName);
+
+ if (!fileInfo) {
+ throw new Error(`No information found for the file ${fileName}.`);
+ }
+
+ let html = `
Nom du fichier : ${fileInfo.fileName}
`;
+ if (fileInfo.expiryDate) {
+ html += `Date d'expiration : ${fileInfo.expiryDate}
`;
+ }
+ if (fileInfo.password) {
+ html += `Mot de passe : Oui
`;
+ }
+ if (fileInfo.userId) {
+ html += `Utilisateur : ${fileInfo.userId}
`;
+ }
+
+ Swal.fire({
+ title: 'Informations sur le fichier',
+ html: html,
+ confirmButtonText: 'Fermer'
+ });
+ } catch (error) {
+ console.error('Error in showFileInfo:', error);
+ Swal.fire({
+ position: 'top',
+ icon: 'error',
+ title: 'Les informations sur le fichier ne sont pas disponibles pour le moment.',
+ text: `Erreur : ${error.message}`,
+ showConfirmButton: false,
+ timer: 1800,
+ toast: true,
+ });
+ }
+ }
+
+ function displayMetadata() {
+ fetch('/build-metadata')
+ .then(response => response.json())
+ .then(metadata => {
+ document.getElementById('buildVersion').textContent = metadata.build_version;
+ document.getElementById('nodeVersion').textContent = metadata.node_version;
+ document.getElementById('expressVersion').textContent = metadata.express_version;
+ document.getElementById('buildSha').textContent = metadata.build_sha;
+ document.getElementById('osType').textContent = metadata.os_type;
+ document.getElementById('osRelease').textContent = metadata.os_release;
+
+ $('#metadataModal').modal('show');
+ })
+ .catch(error => {
+ console.error('Failed to fetch metadata:', error);
+ Swal.fire({
+ icon: 'error',
+ title: 'Erreur',
+ text: 'Impossible de récupérer les métadonnées'
+ });
+ });
+ }
+ document.addEventListener('DOMContentLoaded', () => {
+ // Recherche avec debounce
+ const searchInput = document.getElementById('searchInput');
+ if (searchInput) {
+ let timeout;
+ searchInput.addEventListener('input', (e) => {
+ clearTimeout(timeout);
+ timeout = setTimeout(() => {
+ const term = e.target.value.toLowerCase();
+ document.querySelectorAll('#fileTable tbody tr').forEach(row => {
+ const text = row.querySelector('td:first-child').textContent.toLowerCase();
+ row.style.display = text.includes(term) ? '' : 'none';
+ });
+ }, 150);
+ });
+ }
+
+ // Gestion des modales
+ document.querySelectorAll('[data-toggle="modal"]').forEach(trigger => {
+ trigger.addEventListener('click', () => {
+ const modal = document.querySelector(trigger.dataset.target);
+ if (modal) modal.classList.add('show');
+ });
+ });
+
+ document.querySelectorAll('.modal .close, .modal .btn-secondary').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const modal = btn.closest('.modal');
+ if (modal) modal.classList.remove('show');
+ });
+ });
+
+ // Dropdowns
+ document.querySelectorAll('.dropdown-toggle').forEach(toggle => {
+ toggle.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const menu = toggle.nextElementSibling;
+ if (!menu) return;
+
+ // Fermer les autres dropdowns
+ document.querySelectorAll('.dropdown-menu.show').forEach(m => {
+ if (m !== menu) m.classList.remove('show');
+ });
+
+ menu.classList.toggle('show');
+ });
+ });
+
+ // Fermer les dropdowns au clic extérieur
+ document.addEventListener('click', () => {
+ document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
+ menu.classList.remove('show');
+ });
+ });
+ });
+
+ // Loading overlay
+ window.showLoadingState = () => {
+ const overlay = document.createElement('div');
+ overlay.className = 'loading-overlay animate';
+ overlay.innerHTML = '';
+ document.body.appendChild(overlay);
+ };
+
+ window.hideLoadingState = () => {
+ const overlay = document.querySelector('.loading-overlay');
+ if (overlay) overlay.remove();
+ };
+
+ // Gestion améliorée de l'état de chargement
+window.showLoadingState = () => {
+ const overlay = document.createElement('div');
+ overlay.className = 'loading-overlay';
+
+ const wrapper = document.createElement('div');
+ wrapper.className = 'spinner-wrapper';
+
+ const spinner = document.createElement('div');
+ spinner.className = 'loading-spinner';
+
+ wrapper.appendChild(spinner);
+ overlay.appendChild(wrapper);
+ document.body.appendChild(overlay);
+
+ // Force le reflow pour démarrer l'animation
+ overlay.offsetHeight;
+ overlay.classList.add('show');
+};
+
+window.hideLoadingState = () => {
+ const overlay = document.querySelector('.loading-overlay');
+ if (overlay) {
+ overlay.classList.remove('show');
+ overlay.addEventListener('transitionend', () => overlay.remove(), { once: true });
+ }
+};
+
+// Animation des lignes du tableau
+document.addEventListener('DOMContentLoaded', () => {
+ const tableRows = document.querySelectorAll('.table tr');
+ tableRows.forEach((row, index) => {
+ row.style.setProperty('--row-index', index);
+ requestAnimationFrame(() => row.classList.add('show'));
+ });
+
+ // Animation du conteneur principal
+ const mainContainer = document.querySelector('.form-container');
+ if (mainContainer) {
+ requestAnimationFrame(() => mainContainer.classList.add('show'));
+ }
+});
+
+// Fonction pour les transitions de page
+function transitionToPage(url) {
+ document.body.classList.add('page-transition');
+ showLoadingState();
+
+ setTimeout(() => {
+ window.location.href = url;
+ }, 300);
+}
+
+// Amélioration des modales
+function showModal(modalId) {
+ const modal = document.querySelector(modalId);
+ if (!modal) return;
+
+ modal.style.display = 'flex';
+ requestAnimationFrame(() => {
+ modal.classList.add('show');
+ modal.querySelector('.modal-content')?.classList.add('show');
+ });
+}
+
+function hideModal(modalId) {
+ const modal = document.querySelector(modalId);
+ if (!modal) return;
+
+ modal.querySelector('.modal-content')?.classList.remove('show');
+ modal.classList.remove('show');
+
+ modal.addEventListener('transitionend', () => {
+ modal.style.display = 'none';
+ }, { once: true });
+}
+
+// État de chargement des boutons
+function setButtonLoading(button, isLoading) {
+ if (isLoading) {
+ button.classList.add('loading');
+ button.dataset.originalText = button.innerHTML;
+ button.innerHTML = '';
+ } else {
+ button.classList.remove('loading');
+ if (button.dataset.originalText) {
+ button.innerHTML = button.dataset.originalText;
+ }
+ }
+}
+
+function createLoadingScreen() {
+ const container = document.createElement('div');
+ container.className = 'initial-loading';
+ const content = `
+
+
+
+
Vous y êtes presque !
+
Préparation de votre espace de travail...
+
Chargement des données
+
+
+
+ `;
+ container.innerHTML = content;
+ document.body.appendChild(container);
+ return container;
+}
+
+function initializeLoadingScreen() {
+ // Vérifier si c'est la première visite de la session
+ const hasSeenAnimation = sessionStorage.getItem('hasSeenLoadingAnimation');
+
+ if (hasSeenAnimation) {
+ // Si l'animation a déjà été vue, initialiser directement le contenu
+ const contentWrapper = document.querySelector('.content-wrapper');
+ if (contentWrapper) {
+ contentWrapper.classList.add('loaded');
+ }
+ return Promise.resolve();
+ }
+
+ return new Promise((resolve) => {
+ const loadingScreen = createLoadingScreen();
+
+ setTimeout(() => {
+ loadingScreen.classList.add('fade-out');
+ loadingScreen.addEventListener('animationend', () => {
+ loadingScreen.remove();
+ // Marquer l'animation comme vue pour cette session
+ sessionStorage.setItem('hasSeenLoadingAnimation', 'true');
+ resolve();
+ }, { once: true });
+ }, 2000);
+ });
+}
+
+document.addEventListener('DOMContentLoaded', async function() {
+ try {
+ await initializeLoadingScreen();
+ const contentWrapper = document.querySelector('.content-wrapper');
+ if (contentWrapper) {
+ contentWrapper.classList.add('loaded');
+ }
+ } catch (error) {
+ console.error('Erreur lors du chargement:', error);
+ }
+});
+
+fetch('/build-metadata')
+.then(response => response.json())
+.then(data => {
+ document.getElementById('version-number').textContent = data.build_version;
+})
+.catch(error => {
+ console.error('Error fetching version:', error);
+ document.getElementById('version-number').textContent = 'Version indisponible';
+});
+
+// Fonction pour afficher les détails de collaboration
+function showCollaborationDetails(itemName, itemType) {
+ const modal = $('#collaborationModal');
+ const usersContainer = modal.find('.collaboration-users');
+ const currentItem = { name: itemName, type: itemType };
+
+ // Stockage des informations de l'item actuel
+ modal.data('currentItem', currentItem);
+
+ fetch(`/api/dpanel/collaboration/details/${itemType}/${itemName}`)
+ .then(response => response.json())
+ .then(data => {
+ let userList = '';
+ if (data.activeUsers && data.activeUsers.length > 0) {
+ userList = data.activeUsers.map(user => {
+ const avatarUrl = user.profilePicture || getDefaultAvatar(user.name);
+ return `
+
+

+
+
${user.name}
+
En ligne
+
+
+
+ `;
+ }).join('');
+ } else {
+ userList = 'Aucun utilisateur actif
';
+ }
+ usersContainer.html(userList);
+ modal.modal('show');
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ Swal.fire({
+ icon: 'error',
+ title: 'Erreur',
+ text: 'Impossible de charger les détails de la collaboration'
+ });
+ });
+}
+
+// Gestionnaire de recherche d'utilisateurs
+function searchCollabUser(username) {
+ if (!username) return;
+
+ const resultsDiv = $('#searchCollabResults');
+
+ fetch(`/api/dpanel/collaboration/searchuser?username=${encodeURIComponent(username)}`)
+ .then(response => response.json())
+ .then(result => {
+ if (result.found) {
+ const avatarUrl = result.user.profilePicture || getDefaultAvatar(result.user.name);
+ resultsDiv.html(`
+
+
+

+
${result.user.name}
+
+
+
+ `);
+ } else {
+ resultsDiv.html('Utilisateur non trouvé
');
+ }
+ });
+}
+
+// Initialisation des événements de collaboration
+document.addEventListener('DOMContentLoaded', function() {
+ const modal = $('#collaborationModal');
+
+ // Recherche d'utilisateurs
+ $('#searchCollabBtn').on('click', () => {
+ const username = $('#searchCollabUser').val().trim();
+ searchCollabUser(username);
+ });
+
+ $('#searchCollabUser').on('keypress', (e) => {
+ if (e.key === 'Enter') {
+ const username = e.target.value.trim();
+ searchCollabUser(username);
+ }
+ });
+
+ // Gestion de l'ajout de collaborateurs
+ $(document).on('click', '.add-collab-btn', function() {
+ const userId = $(this).data('user-id');
+ const currentItem = modal.data('currentItem');
+
+ addCollaborator(currentItem.name, currentItem.type, userId);
+ });
+
+ // Gestion de la suppression de collaborateurs
+ $(document).on('click', '.remove-collab-btn', function() {
+ const userId = $(this).data('user-id');
+ const currentItem = modal.data('currentItem');
+
+ removeCollaborator(currentItem.name, currentItem.type, userId);
+ });
+
+ // Désactivation de la collaboration
+ $('#disableCollabBtn').on('click', function() {
+ const currentItem = modal.data('currentItem');
+ modal.modal('hide');
+ toggleCollaboration(currentItem.name, currentItem.type, false);
+ });
+});
+
+function addCollaborator(itemName, itemType, userId) {
+ fetch('/api/dpanel/collaboration/add', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ itemName, itemType, userId })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showCollaborationDetails(itemName, itemType); // Rafraîchir la liste
+ Swal.fire({
+ position: 'top-end',
+ icon: 'success',
+ title: 'Collaborateur ajouté',
+ showConfirmButton: false,
+ timer: 1500,
+ toast: true
+ });
+ }
+ })
+ .catch(error => console.error('Error:', error));
+}
+
+function removeCollaborator(itemName, itemType, userId) {
+ fetch('/api/dpanel/collaboration/remove', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ itemName, itemType, userId })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showCollaborationDetails(itemName, itemType); // Rafraîchir la liste
+ Swal.fire({
+ position: 'top-end',
+ icon: 'success',
+ title: 'Collaborateur retiré',
+ showConfirmButton: false,
+ timer: 1500,
+ toast: true
+ });
+ }
+ })
+ .catch(error => console.error('Error:', error));
+}
+
+// Fonction pour afficher les détails de collaboration
+function showCollaborationDetails(itemName, itemType) {
+ // Récupérer le modal
+ const modal = $('#collaborationModal');
+ const usersContainer = modal.find('.collaboration-users');
+
+ fetch(`/api/dpanel/collaboration/details/${itemType}/${itemName}`)
+ .then(response => response.json())
+ .then(data => {
+ let userList = '';
+ if (data.activeUsers && data.activeUsers.length > 0) {
+ userList = data.activeUsers.map(user => {
+ const avatarUrl = user.profilePicture || getDefaultAvatar(user.name);
+ return `
+
+

+
+
${user.name}
+
En ligne
+
+
+
+ `;
+ }).join('');
+ } else {
+ userList = 'Aucun utilisateur actif
';
+ }
+
+ usersContainer.html(userList);
+
+ // Gestionnaire pour la recherche d'utilisateurs
+ const searchInput = $('#searchCollabUser');
+ const searchBtn = $('#searchCollabBtn');
+ const resultsDiv = $('#searchCollabResults');
+
+ searchBtn.off('click').on('click', () => searchCollabUser(searchInput.val().trim()));
+ searchInput.off('keypress').on('keypress', (e) => {
+ if (e.key === 'Enter') {
+ searchCollabUser(searchInput.val().trim());
+ }
+ });
+
+ // Gestionnaire pour la désactivation de la collaboration
+ $('#disableCollaborationBtn').off('click').on('click', () => {
+ modal.modal('hide');
+ toggleCollaboration(itemName, itemType, false);
+ });
+
+ // Afficher le modal
+ modal.modal('show');
+ })
+ .catch(error => {
+ console.error('Error fetching collaboration details:', error);
+ Swal.fire({
+ icon: 'error',
+ title: 'Erreur',
+ text: 'Impossible de charger les détails de la collaboration'
+ });
+ });
+}
+
+function searchCollabUser(username) {
+ if (!username) return;
+
+ fetch(`/api/dpanel/collaboration/searchuser?username=${encodeURIComponent(username)}`)
+ .then(response => response.json())
+ .then(result => {
+ const resultsDiv = $('#searchCollabResults');
+ if (result.found) {
+ const avatarUrl = result.user.profilePicture || getDefaultAvatar(result.user.name);
+ resultsDiv.html(`
+
+
+

+
${result.user.name}
+
+
+
+ `);
+ } else {
+ resultsDiv.html('Utilisateur non trouvé
');
+ }
+ });
+}
+
+// Fonction pour gérer l'activation/désactivation de la collaboration
+function toggleCollaboration(itemName, itemType, enable) {
+ const itemLabel = itemType === 'folder' ? 'dossier' : 'fichier';
+
+ const performToggle = () => {
+ fetch('/api/dpanel/collaboration/toggle', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ itemName,
+ itemType,
+ enable
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ updateCollaborationUI({
+ itemName,
+ itemType,
+ isCollaborative: enable,
+ activeUsers: data.activeUsers || []
+ });
+
+ Swal.fire({
+ position: 'top',
+ icon: 'success',
+ title: `Collaboration ${enable ? 'activée' : 'désactivée'}`,
+ showConfirmButton: false,
+ timer: 1500,
+ toast: true
+ });
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ Swal.fire({
+ position: 'top',
+ icon: 'error',
+ title: 'Erreur lors de la modification de la collaboration',
+ showConfirmButton: false,
+ timer: 1500,
+ toast: true
+ });
+ });
+ };
+
+ if (!enable) {
+ performToggle();
+ } else {
+ Swal.fire({
+ title: 'Activer la collaboration ?',
+ text: `D'autres utilisateurs pourront accéder à ce ${itemLabel} en temps réel.`,
+ icon: 'question',
+ showCancelButton: true,
+ confirmButtonText: 'Activer',
+ cancelButtonText: 'Annuler'
+ }).then((result) => {
+ if (result.isConfirmed) {
+ performToggle();
+ }
+ });
+ }
+}
+
+// Fonction pour mettre à jour l'interface utilisateur
+function updateCollaborationUI(data) {
+ const row = document.querySelector(`tr[data-name="${data.itemName}"]`);
+ if (!row) return;
+
+ const button = row.querySelector('.toggle-collaboration-btn');
+ const nameCell = row.querySelector('td:first-child');
+
+ if (button) {
+ button.setAttribute('data-is-collaborative', data.isCollaborative);
+ button.title = data.isCollaborative ? 'Voir les collaborateurs' : 'Activer la collaboration';
+ button.classList.toggle('active', data.isCollaborative);
+ }
+
+ let badge = nameCell.querySelector('.collaboration-badge');
+ if (data.isCollaborative) {
+ if (!badge) {
+ badge = document.createElement('span');
+ badge.className = 'ml-2 badge badge-info collaboration-badge';
+ badge.innerHTML = ' ';
+ nameCell.querySelector('div').appendChild(badge);
+ }
+
+ const countSpan = badge.querySelector('.active-users-count');
+ if (data.activeUsers && data.activeUsers.length > 0) {
+ badge.classList.remove('badge-secondary');
+ badge.classList.add('badge-info');
+ countSpan.textContent = ` ${data.activeUsers.length}`;
+ badge.title = `Collaborateurs actifs : ${data.activeUsers.map(u => u.name).join(', ')}`;
+ } else {
+ badge.classList.remove('badge-info');
+ badge.classList.add('badge-secondary');
+ countSpan.textContent = '';
+ badge.title = 'Aucun collaborateur actif';
+ }
+ } else if (badge) {
+ badge.remove();
+ }
+}
+
+// Initialisation au chargement de la page
+document.addEventListener('DOMContentLoaded', async function() {
+ // Initialiser WebSocket
+ const ws = new WebSocket(`ws://${window.location.host}`);
+
+ ws.onmessage = function(event) {
+ const data = JSON.parse(event.data);
+ if (data.type === 'collaborationStatus') {
+ updateCollaborationUI(data);
+ }
+ };
+
+ // Charger le statut initial de collaboration pour tous les éléments
+ try {
+ const response = await fetch('/api/dpanel/collaboration/status');
+ const data = await response.json();
+ if (data.items) {
+ Object.entries(data.items).forEach(([itemId, status]) => {
+ // Pour chaque élément dans le fichier collaboration.json
+ const [type, name] = itemId.split('-');
+ updateCollaborationUI({
+ itemName: name,
+ itemType: type,
+ isCollaborative: status.isCollaborative,
+ activeUsers: status.activeUsers
+ });
+ });
+ }
+ } catch (error) {
+ console.error('Error loading collaboration status:', error);
+ }
+
+ // Gestionnaire pour les boutons de collaboration
+ document.querySelectorAll('.toggle-collaboration-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const itemName = this.getAttribute('data-item-name');
+ const itemType = this.getAttribute('data-item-type');
+ const isCollaborative = this.getAttribute('data-is-collaborative') === 'true';
+
+ if (isCollaborative) {
+ showCollaborationDetails(itemName, itemType);
+ } else {
+ toggleCollaboration(itemName, itemType, !isCollaborative);
+ }
+ });
+ });
+
+ // Joindre automatiquement si on est sur un élément collaboratif
+ const currentPath = window.location.pathname;
+ const matches = currentPath.match(/\/folder\/(.+)$/);
+ if (matches) {
+ const folderName = decodeURIComponent(matches[1]);
+ joinCollaboration(folderName, 'folder');
+ }
+});
+
+// Initialisation au chargement de la page
+document.addEventListener('DOMContentLoaded', function() {
+ // Initialiser WebSocket
+ const ws = new WebSocket(`ws://${window.location.host}`);
+
+ ws.onmessage = function(event) {
+ const data = JSON.parse(event.data);
+ if (data.type === 'collaborationStatus') {
+ updateCollaborationUI(data);
+ }
+ };
+
+ // Gestionnaire pour les boutons de collaboration
+ document.querySelectorAll('.toggle-collaboration-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const itemName = this.getAttribute('data-item-name');
+ const itemType = this.getAttribute('data-item-type');
+ const isCollaborative = this.getAttribute('data-is-collaborative') === 'true';
+
+ if (isCollaborative) {
+ showCollaborationDetails(itemName, itemType);
+ } else {
+ toggleCollaboration(itemName, itemType, !isCollaborative);
+ }
+ });
+ });
+});
+
+document.addEventListener('DOMContentLoaded', function() {
+ const contextMenu = document.querySelector('.context-menu');
+ let selectedItem = null;
+
+ // Activer le menu contextuel sur les lignes du tableau
+ document.querySelectorAll('#fileTable tbody tr').forEach(row => {
+ row.addEventListener('contextmenu', function(e) {
+ e.preventDefault(); // Empêcher le menu contextuel par défaut
+
+ selectedItem = {
+ type: this.dataset.type,
+ name: this.dataset.name,
+ isCollaborative: this.querySelector('.toggle-collaboration-btn')?.dataset.isCollaborative === 'true'
+ };
+
+ // Ajuster la visibilité des options du menu en fonction du type
+ adjustMenuOptions(selectedItem);
+
+ // Positionner le menu contextuel
+ showContextMenu(e.pageX, e.pageY);
+ });
+ });
+
+ // Fermer le menu au clic en dehors
+ document.addEventListener('click', function(e) {
+ if (!contextMenu.contains(e.target)) {
+ hideContextMenu();
+ }
+ });
+
+ // Gérer les actions du menu contextuel
+ document.querySelectorAll('.context-menu .menu-item').forEach(item => {
+ item.addEventListener('click', function(e) {
+ e.preventDefault();
+ const action = this.dataset.action;
+
+ if (selectedItem) {
+ handleMenuAction(action, selectedItem);
+ }
+
+ hideContextMenu();
+ });
+ });
+
+ // Fonction pour ajuster les options du menu selon le type d'élément
+ function adjustMenuOptions(item) {
+ const menuItems = contextMenu.querySelectorAll('.menu-item');
+
+ menuItems.forEach(menuItem => {
+ const action = menuItem.dataset.action;
+
+ // Gérer la visibilité des options selon le type
+ switch(action) {
+ case 'open':
+ menuItem.style.display = item.type.includes('folder') ? 'flex' : 'none';
+ break;
+ case 'collaborate':
+ menuItem.style.display = item.type === 'folder' ? 'flex' : 'none';
+ menuItem.querySelector('span').textContent =
+ item.isCollaborative ? 'Gérer la collaboration' : 'Activer la collaboration';
+ break;
+ case 'share':
+ menuItem.style.display = item.type === 'file' ? 'flex' : 'none';
+ break;
+ // Vous pouvez ajouter d'autres cas selon vos besoins
+ }
+ });
+ }
+
+ // Fonction pour afficher le menu contextuel
+ function showContextMenu(x, y) {
+ const menu = contextMenu;
+ menu.style.display = 'block';
+
+ // Ajuster la position si le menu dépasse de la fenêtre
+ const menuRect = menu.getBoundingClientRect();
+ const windowWidth = window.innerWidth;
+ const windowHeight = window.innerHeight;
+
+ if (x + menuRect.width > windowWidth) {
+ x = windowWidth - menuRect.width;
+ }
+
+ if (y + menuRect.height > windowHeight) {
+ y = windowHeight - menuRect.height;
+ }
+
+ menu.style.left = `${x}px`;
+ menu.style.top = `${y}px`;
+ }
+
+ // Fonction pour cacher le menu contextuel
+ function hideContextMenu() {
+ contextMenu.style.display = 'none';
+ }
+
+ // Fonction pour gérer les actions du menu
+ function handleMenuAction(action, item) {
+ switch(action) {
+ case 'open':
+ if (item.type === 'folder') {
+ window.location.href = `/dpanel/dashboard/folder/${encodeURIComponent(item.name)}`;
+ }
+ break;
+
+ case 'rename':
+ Swal.fire({
+ title: 'Renommer',
+ input: 'text',
+ inputValue: item.name,
+ showCancelButton: true,
+ confirmButtonText: 'Renommer',
+ cancelButtonText: 'Annuler',
+ inputValidator: (value) => {
+ if (!value) {
+ return 'Le nom ne peut pas être vide';
+ }
+ }
+ }).then((result) => {
+ if (result.isConfirmed) {
+ // Appeler votre API de renommage ici
+ console.log(`Renommer ${item.name} en ${result.value}`);
+ }
+ });
+ break;
+
+ case 'delete':
+ Swal.fire({
+ title: 'Êtes-vous sûr ?',
+ text: `Voulez-vous vraiment supprimer "${item.name}" ?`,
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: 'hsl(var(--destructive))',
+ confirmButtonText: 'Supprimer',
+ cancelButtonText: 'Annuler'
+ }).then((result) => {
+ if (result.isConfirmed) {
+ // Appeler votre API de suppression ici
+ console.log(`Supprimer ${item.name}`);
+ }
+ });
+ break;
+
+ case 'collaborate':
+ if (item.isCollaborative) {
+ showCollaborationDetails(item.name, item.type);
+ } else {
+ toggleCollaboration(item.name, item.type, true);
+ }
+ break;
+
+ case 'move':
+ // Implémenter la logique de déplacement
+ console.log(`Déplacer ${item.name}`);
+ break;
+
+ case 'share':
+ if (item.type === 'file') {
+ // Copier le lien de partage dans le presse-papier
+ const shareButton = document.querySelector(`[data-file-name="${item.name}"] .copy-button`);
+ const fileUrl = shareButton?.dataset.fileUrl;
+ if (fileUrl) {
+ navigator.clipboard.writeText(fileUrl)
+ .then(() => {
+ Swal.fire({
+ position: 'top-end',
+ icon: 'success',
+ title: 'Lien copié !',
+ showConfirmButton: false,
+ timer: 1500,
+ toast: true
+ });
+ });
+ }
+ }
+ break;
+ }
+ }
+});
+
+
+document.addEventListener('DOMContentLoaded', function() {
+ // Gestion de la suppression des dossiers
+ document.querySelectorAll('.delete-folder-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const folderName = this.dataset.folderName;
+
+ Swal.fire({
+ title: 'Êtes-vous sûr ?',
+ text: `Voulez-vous vraiment supprimer le dossier "${folderName}" ?`,
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#dc3545',
+ confirmButtonText: 'Supprimer',
+ cancelButtonText: 'Annuler'
+ }).then((result) => {
+ if (result.isConfirmed) {
+ // Appel à votre API pour supprimer le dossier
+ fetch(`/api/dpanel/folders/delete/${encodeURIComponent(folderName)}`, {
+ method: 'DELETE'
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ // Supprimer la ligne du tableau
+ const row = button.closest('tr');
+ row.remove();
+
+ // Afficher une notification de succès
+ Swal.fire({
+ position: 'top-end',
+ icon: 'success',
+ title: 'Dossier supprimé !',
+ showConfirmButton: false,
+ timer: 1500,
+ toast: true
+ });
+ } else {
+ throw new Error(data.message || 'Erreur lors de la suppression');
+ }
+ })
+ .catch(error => {
+ Swal.fire({
+ position: 'top-end',
+ icon: 'error',
+ title: 'Erreur lors de la suppression',
+ text: error.message,
+ showConfirmButton: false,
+ timer: 3000,
+ toast: true
+ });
+ });
+ }
+ });
+ });
+ });
+
+ // Gestion du renommage des dossiers
+ document.querySelectorAll('.rename-folder-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const folderName = this.dataset.folderName;
+
+ Swal.fire({
+ title: 'Renommer le dossier',
+ input: 'text',
+ inputValue: folderName,
+ showCancelButton: true,
+ confirmButtonText: 'Renommer',
+ cancelButtonText: 'Annuler',
+ inputValidator: (value) => {
+ if (!value) {
+ return 'Veuillez entrer un nom de dossier';
+ }
+ }
+ }).then((result) => {
+ if (result.isConfirmed) {
+ const newName = result.value;
+ // Appel à votre API pour renommer le dossier
+ fetch('/api/dpanel/folders/rename', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ oldName: folderName,
+ newName: newName
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ // Mettre à jour l'affichage
+ const row = button.closest('tr');
+ row.querySelector('td:first-child').textContent = newName;
+
+ // Mettre à jour les attributs data
+ row.dataset.name = newName;
+ button.dataset.folderName = newName;
+ button.dataset.itemName = newName;
+
+ // Mettre à jour le lien d'ouverture
+ const openLink = row.querySelector('a.dropdown-item');
+ if (openLink) {
+ openLink.href = `/dpanel/dashboard/folder/${encodeURIComponent(newName)}`;
+ }
+
+ Swal.fire({
+ position: 'top-end',
+ icon: 'success',
+ title: 'Dossier renommé !',
+ showConfirmButton: false,
+ timer: 1500,
+ toast: true
+ });
+ } else {
+ throw new Error(data.message || 'Erreur lors du renommage');
+ }
+ })
+ .catch(error => {
+ Swal.fire({
+ position: 'top-end',
+ icon: 'error',
+ title: 'Erreur lors du renommage',
+ text: error.message,
+ showConfirmButton: false,
+ timer: 3000,
+ toast: true
+ });
+ });
+ }
+ });
+ });
+ });
+});
+
+document.querySelectorAll('tr[data-type="folder"] td:first-child, tr[data-type="shared-folder"] td:first-child').forEach(cell => {
+ cell.addEventListener('dblclick', (e) => {
+ e.preventDefault(); // Prevent text selection
+ const row = cell.closest('tr');
+ const url = row.dataset.url;
+ if (url) {
+ window.location.href = url;
+ }
+ });
+});
+
+// Disable text selection on double-click for folder names
+document.querySelectorAll('tr[data-type="folder"] td:first-child, tr[data-type="shared-folder"] td:first-child').forEach(cell => {
+ cell.style.userSelect = 'none';
+});
+
+document.addEventListener('DOMContentLoaded', () => {
+ const searchContainer = document.querySelector('.flex.justify-between.items-center');
+ const button = document.createElement('button');
+ button.className = 'btn btn-secondary ml-2';
+ button.innerHTML = '';
+ searchContainer.appendChild(button);
+
+ const table = document.querySelector('#fileTable tbody');
+ let isGridView = localStorage.getItem('isGridView') === 'true';
+
+ function buildGridLayout(tr) {
+ const firstCell = tr.querySelector('td:first-child');
+ const name = firstCell.querySelector('div').innerText.trim();
+ const icon = firstCell.querySelector('i').cloneNode(true);
+ const type = tr.querySelector('td:nth-child(2)').innerText.trim();
+ const owner = tr.querySelector('td:nth-child(3)').innerText.trim();
+ const size = tr.querySelector('td:nth-child(4)').innerText.trim();
+ const collab = firstCell.querySelector('.collaboration-badge');
+
+ const content = document.createElement('div');
+ content.className = 'icon-container';
+ content.innerHTML = `
+ ${icon.outerHTML}
+ ${name}
+ ${type} • ${size}
+ `;
+
+ firstCell.innerHTML = '';
+ firstCell.appendChild(content);
+ if (collab) {
+ firstCell.appendChild(collab);
+ }
+ }
+
+ function toggleView(e) {
+ e?.preventDefault();
+ isGridView = !table.classList.contains('grid-view');
+
+ if (isGridView) {
+ table.classList.add('grid-view');
+ table.querySelectorAll('tr').forEach(buildGridLayout);
+ button.innerHTML = '';
+ } else {
+ table.classList.remove('grid-view');
+ location.reload();
+ button.innerHTML = '';
+ }
+
+ localStorage.setItem('isGridView', isGridView);
+ }
+
+ button.addEventListener('click', toggleView);
+
+ if (isGridView) {
+ table.classList.add('grid-view');
+ table.querySelectorAll('tr').forEach(buildGridLayout);
+ button.innerHTML = '';
+ }
+});
+
+// Améliorations de la vue grille
+function toggleGridView() {
+ const table = document.querySelector('#fileTable tbody');
+ const isGridView = table.classList.contains('grid-view');
+
+ if (!isGridView) {
+ table.classList.add('grid-view');
+ document.querySelectorAll('#fileTable tbody tr').forEach(tr => {
+ const firstCell = tr.querySelector('td:first-child');
+ const icon = firstCell.querySelector('i').cloneNode(true);
+ const name = firstCell.textContent.trim();
+ const type = tr.querySelector('td:nth-child(2)').textContent.trim();
+
+ const container = document.createElement('div');
+ container.className = 'icon-container';
+ container.innerHTML = `
+ ${icon.outerHTML}
+ ${name}
+ ${type}
+ `;
+
+ firstCell.innerHTML = '';
+ firstCell.appendChild(container);
+ });
+ } else {
+ table.classList.remove('grid-view');
+ location.reload(); // Recharger pour restaurer la vue liste
+ }
+
+ localStorage.setItem('viewMode', isGridView ? 'list' : 'grid');
+}
+
+// Gestionnaire du menu contextuel amélioré
+function handleContextMenu(e) {
+ e.preventDefault();
+
+ const target = e.target.closest('tr');
+ if (!target) return;
+
+ const contextMenu = document.querySelector('.context-menu');
+ contextMenu.style.display = 'block';
+
+ // Positionnement intelligent du menu
+ const x = e.pageX;
+ const y = e.pageY;
+ const winWidth = window.innerWidth;
+ const winHeight = window.innerHeight;
+ const menuWidth = contextMenu.offsetWidth;
+ const menuHeight = contextMenu.offsetHeight;
+
+ contextMenu.style.left = (x + menuWidth > winWidth ? winWidth - menuWidth : x) + 'px';
+ contextMenu.style.top = (y + menuHeight > winHeight ? winHeight - menuHeight : y) + 'px';
+
+ // Stockage de la référence à l'élément
+ contextMenu.dataset.targetName = target.dataset.name;
+ contextMenu.dataset.targetType = target.dataset.type;
+
+ // Mise à jour des actions du menu
+ updateContextMenuActions(target);
+}
+
+function updateContextMenuActions(target) {
+ const contextMenu = document.querySelector('.context-menu');
+ const actions = contextMenu.querySelectorAll('.menu-item');
+
+ actions.forEach(action => {
+ action.onclick = (e) => {
+ e.preventDefault();
+ const name = contextMenu.dataset.targetName;
+ const type = contextMenu.dataset.targetType;
+
+ switch(action.dataset.action) {
+ case 'open':
+ if (type === 'folder') {
+ window.location.href = `/dpanel/dashboard/folder/${encodeURIComponent(name)}`;
+ }
+ break;
+ case 'rename':
+ if (type === 'folder') {
+ renameFolder(name);
+ } else {
+ renameFile(name);
+ }
+ break;
+ case 'delete':
+ if (type === 'folder') {
+ confirmDeleteFolder(name);
+ } else {
+ confirmDelete(name);
+ }
+ break;
+ case 'share':
+ if (type === 'file') {
+ const url = target.dataset.url;
+ copyFileLink(url);
+ }
+ break;
+ }
+
+ contextMenu.style.display = 'none';
+ };
+ });
+}
+
+// Initialisation
+document.addEventListener('DOMContentLoaded', () => {
+ // Restaurer la vue précédente
+ const viewMode = localStorage.getItem('viewMode');
+ if (viewMode === 'grid') {
+ toggleGridView();
+ }
+
+ // Gestionnaire du bouton de vue
+ document.querySelector('.view-toggle').addEventListener('click', toggleGridView);
+
+ // Gestionnaire du menu contextuel
+ document.addEventListener('contextmenu', handleContextMenu);
+ document.addEventListener('click', (e) => {
+ if (!e.target.closest('.context-menu')) {
+ document.querySelector('.context-menu').style.display = 'none';
+ }
+ });
+});
+
+function getDefaultAvatar(username) {
+ const encodedName = encodeURIComponent(username);
+ return `https://api.dicebear.com/7.x/initials/svg?seed=${encodedName}&background=%234e54c8&radius=50`;
+}
+
+// Modifiez la fonction qui affiche les détails de collaboration
+function showCollaborationDetails(itemName, itemType) {
+ fetch(`/api/dpanel/collaboration/details/${itemType}/${itemName}`)
+ .then(response => response.json())
+ .then(data => {
+ let userList = '';
+ if (data.activeUsers && data.activeUsers.length > 0) {
+ userList = data.activeUsers.map(user => {
+ const avatarUrl = user.profilePicture || getDefaultAvatar(user.name);
+ return `
+
+

+
${user.name}
+
+ `;
+ }).join('');
+ } else {
+ userList = 'Aucun utilisateur actif
';
+ }
+ });
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ // Récupération des éléments nécessaires
+ const tableBody = document.querySelector('#fileTable tbody');
+ const searchContainer = document.querySelector('.flex.justify-between.items-center');
+
+ if (!tableBody || !searchContainer) return; // Protection contre les éléments manquants
+
+ // Création du bouton de basculement
+ const viewToggleBtn = document.createElement('button');
+ viewToggleBtn.className = 'btn btn-secondary ml-2 view-toggle-btn';
+ viewToggleBtn.innerHTML = '';
+ searchContainer.appendChild(viewToggleBtn);
+
+ // Récupération du mode de vue sauvegardé
+ let isGridView = localStorage.getItem('isGridView') === 'true';
+
+ function buildGridLayout(tr) {
+ if (!tr) return;
+
+ const firstCell = tr.querySelector('td:first-child');
+ if (!firstCell) return;
+
+ const name = firstCell.innerText.trim();
+ const icon = firstCell.querySelector('i')?.cloneNode(true);
+ const type = tr.querySelector('td:nth-child(2)')?.innerText.trim() || '';
+ const owner = tr.querySelector('td:nth-child(3)')?.innerText.trim() || '';
+ const size = tr.querySelector('td:nth-child(4)')?.innerText.trim() || '';
+ const collab = firstCell.querySelector('.collaboration-badge');
+
+ if (!icon) return;
+
+ const content = document.createElement('div');
+ content.className = 'icon-container';
+ content.innerHTML = `
+ ${icon.outerHTML}
+ ${name}
+ ${type} • ${size}
+ `;
+
+ firstCell.innerHTML = '';
+ firstCell.appendChild(content);
+ if (collab) {
+ firstCell.appendChild(collab);
+ }
+ }
+
+ function toggleView() {
+ isGridView = !tableBody.classList.contains('grid-view');
+
+ if (isGridView) {
+ tableBody.classList.add('grid-view');
+ document.querySelectorAll('#fileTable tbody tr').forEach(buildGridLayout);
+ viewToggleBtn.innerHTML = '';
+ } else {
+ tableBody.classList.remove('grid-view');
+ location.reload(); // Recharge la page pour restaurer la vue liste
+ viewToggleBtn.innerHTML = '';
+ }
+
+ localStorage.setItem('isGridView', isGridView);
+ }
+
+ // Ajout du gestionnaire d'événements
+ viewToggleBtn.addEventListener('click', toggleView);
+
+ // Initialisation de la vue au chargement
+ if (isGridView) {
+ tableBody.classList.add('grid-view');
+ document.querySelectorAll('#fileTable tbody tr').forEach(buildGridLayout);
+ viewToggleBtn.innerHTML = '';
+ }
+});
+
+// ...existing code...
+
+// Gestion des collaborations (remplacez ou ajoutez cette partie)
+function showCollaborationModal(itemName, itemType) {
+ const modalHtml = `
+
+
+
Collaborateurs actifs
+
+
+
+
Ajouter un collaborateur
+
+
+
+
+ `;
+
+ Swal.fire({
+ title: 'Gestion de la collaboration',
+ html: modalHtml,
+ showCancelButton: true,
+ showDenyButton: true,
+ confirmButtonText: 'Fermer',
+ denyButtonText: 'Désactiver la collaboration',
+ denyButtonColor: '#dc3545',
+ width: '600px',
+ didOpen: () => {
+ // Charger les utilisateurs actifs
+ loadActiveUsers(itemName, itemType);
+
+ // Configuration de la recherche
+ const searchInput = Swal.getPopup().querySelector('#searchUserInput');
+ const searchBtn = Swal.getPopup().querySelector('#searchUserBtn');
+
+ searchBtn.addEventListener('click', () => searchUsers(searchInput.value, itemName, itemType));
+ searchInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') {
+ searchUsers(searchInput.value, itemName, itemType);
+ }
+ });
+ }
+ }).then((result) => {
+ if (result.isDenied) {
+ disableCollaboration(itemName, itemType);
+ }
+ });
+}
+
+// Fonction de chargement des utilisateurs actifs
+function loadActiveUsers(itemName, itemType) {
+ fetch(`/api/dpanel/collaboration/users/${itemType}/${itemName}`)
+ .then(response => response.json())
+ .then(data => {
+ const usersList = Swal.getPopup().querySelector('#activeUsersList');
+ if (!usersList) return;
+
+ if (data.users && data.users.length > 0) {
+ const usersHtml = data.users.map(user => `
+
+
+
})
+
+
${user.name}
+
En ligne
+
+
+
+
+ `).join('');
+ usersList.innerHTML = usersHtml;
+ } else {
+ usersList.innerHTML = 'Aucun collaborateur actif
';
+ }
+ })
+ .catch(error => {
+ console.error('Erreur lors du chargement des utilisateurs:', error);
+ });
+}
+
+// Fonction de recherche d'utilisateurs
+function searchUsers(searchTerm, itemName, itemType) {
+ if (!searchTerm.trim()) return;
+
+ const resultsDiv = Swal.getPopup().querySelector('#searchResults');
+ if (!resultsDiv) return;
+
+ resultsDiv.innerHTML = ' Recherche...
';
+
+ fetch(`/api/dpanel/users/search?term=${encodeURIComponent(searchTerm)}`)
+ .then(response => response.json())
+ .then(data => {
+ if (data.users && data.users.length > 0) {
+ const resultsHtml = data.users.map(user => `
+
+
+
+
})
+
${user.name}
+
+
+
+
+ `).join('');
+ resultsDiv.innerHTML = resultsHtml;
+ } else {
+ resultsDiv.innerHTML = 'Aucun utilisateur trouvé
';
+ }
+ })
+ .catch(error => {
+ console.error('Erreur lors de la recherche:', error);
+ resultsDiv.innerHTML = 'Erreur lors de la recherche
';
+ });
+}
+
+// Ajoutez cet écouteur d'événements pour le bouton de collaboration
+document.addEventListener('DOMContentLoaded', function() {
+ document.querySelectorAll('.toggle-collaboration-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const itemName = this.getAttribute('data-item-name');
+ const itemType = this.getAttribute('data-item-type');
+ const isCollaborative = this.getAttribute('data-is-collaborative') === 'true';
+
+ if (isCollaborative) {
+ showCollaborationModal(itemName, itemType);
+ } else {
+ toggleCollaboration(itemName, itemType, true);
+ }
+ });
+ });
+});
+// Remplacez ou ajoutez cette fonction dans votre fichier dashboard.js
+function showCollaborationModal(itemName, itemType) {
+ Swal.fire({
+ title: 'Gestion de la collaboration',
+ html: `
+
+
+
Collaborateurs actifs
+
+
+
+
Ajouter un collaborateur
+
+
+
+
+ `,
+ showCancelButton: true,
+ showDenyButton: true,
+ confirmButtonText: 'Fermer',
+ denyButtonText: 'Désactiver la collaboration',
+ denyButtonColor: '#dc3545',
+ width: '600px',
+ customClass: {
+ container: 'collaboration-modal',
+ popup: 'collaboration-popup',
+ content: 'collaboration-content'
+ },
+ didOpen: () => {
+ loadActiveUsers(itemName, itemType);
+ setupSearchHandlers(itemName, itemType);
+ }
+ }).then((result) => {
+ if (result.isDenied) {
+ disableCollaboration(itemName, itemType);
+ }
+ });
+}
+
+// Modifier l'événement click des boutons de collaboration
+document.addEventListener('DOMContentLoaded', function() {
+ document.querySelectorAll('.toggle-collaboration-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const itemName = this.getAttribute('data-item-name');
+ const itemType = this.getAttribute('data-item-type');
+ const isCollaborative = this.getAttribute('data-is-collaborative') === 'true';
+
+ if (isCollaborative) {
+ showCollaborationModal(itemName, itemType);
+ } else {
+ toggleCollaboration(itemName, itemType, true);
+ }
+ });
+ });
+});
+
+function loadActiveUsers(itemName, itemType) {
+ const usersList = Swal.getPopup().querySelector('#activeUsersList');
+
+ fetch(`/api/dpanel/collaboration/users/${itemType}/${itemName}`)
+ .then(response => response.json())
+ .then(data => {
+ if (!usersList) return;
+
+ if (data.users && data.users.length > 0) {
+ usersList.innerHTML = data.users.map(user => `
+
+
+
})
+
+
${user.name}
+
En ligne
+
+
+
+
+ `).join('');
+ } else {
+ usersList.innerHTML = 'Aucun collaborateur actif
';
+ }
+ })
+ .catch(error => {
+ console.error('Erreur:', error);
+ usersList.innerHTML = 'Erreur lors du chargement
';
+ });
+}
+
+function setupSearchHandlers(itemName, itemType) {
+ const searchInput = Swal.getPopup().querySelector('#searchUserInput');
+ const searchBtn = Swal.getPopup().querySelector('#searchUserBtn');
+
+ if (!searchInput || !searchBtn) return;
+
+ searchBtn.onclick = () => searchUsers(searchInput.value, itemName, itemType);
+ searchInput.onkeypress = (e) => {
+ if (e.key === 'Enter') {
+ searchUsers(searchInput.value, itemName, itemType);
+ }
+ };
+}
+
+function searchUsers(searchTerm, itemName, itemType) {
+ if (!searchTerm.trim()) return;
+
+ const resultsDiv = Swal.getPopup().querySelector('#searchResults');
+ if (!resultsDiv) return;
+
+ resultsDiv.innerHTML = ' Recherche...
';
+
+ fetch(`/api/dpanel/users/search?term=${encodeURIComponent(searchTerm)}`)
+ .then(response => response.json())
+ .then(data => {
+ if (data.users && data.users.length > 0) {
+ resultsDiv.innerHTML = data.users.map(user => `
+
+
+
+
})
+
${user.name}
+
+
+
+
+ `).join('');
+ } else {
+ resultsDiv.innerHTML = 'Aucun utilisateur trouvé
';
+ }
+ })
+ .catch(error => {
+ console.error('Erreur:', error);
+ resultsDiv.innerHTML = 'Erreur lors de la recherche
';
+ });
+}
\ No newline at end of file
diff --git a/public/js/dashboard.js b/public/js/dashboard.js
index 4a9781f..c5f0fcd 100644
--- a/public/js/dashboard.js
+++ b/public/js/dashboard.js
@@ -1,738 +1,1330 @@
- document.addEventListener('DOMContentLoaded', function() {
- document.querySelectorAll('.file-size').forEach(function(element) {
- const size = parseInt(element.getAttribute('data-size'));
- element.textContent = formatFileSize(size);
+// Dashboard JavaScript - Version corrigée
+document.addEventListener('DOMContentLoaded', function() {
+ // Initialisation générale
+ initializeDashboard();
+ initializeContextMenu();
+ initializeDropdowns();
+ initializeGridView();
+ initializeCollaboration();
+ initializeFileHandlers();
+});
+
+// =================== INITIALISATION ===================
+function initializeDashboard() {
+ // Formatage des tailles de fichiers
+ document.querySelectorAll('.file-size').forEach(function(element) {
+ const size = parseInt(element.getAttribute('data-size'));
+ element.textContent = formatFileSize(size);
+ });
+
+ // Événements de base
+ document.getElementById('searchButton')?.addEventListener('click', searchFiles);
+ document.getElementById('newFolderBtn')?.addEventListener('click', showNewFolderModal);
+ document.getElementById('themeSwitcher')?.addEventListener('click', toggleDarkMode);
+
+ // Thème
+ initTheme();
+
+ // Version
+ loadVersion();
+
+ // Double-clic sur les dossiers
+ document.querySelectorAll('tr[data-type="folder"] td:first-child, tr[data-type="shared-folder"] td:first-child').forEach(cell => {
+ cell.style.userSelect = 'none';
+ cell.addEventListener('dblclick', (e) => {
+ e.preventDefault();
+ const row = cell.closest('tr');
+ const url = row.dataset.url;
+ if (url) window.location.href = url;
});
+ });
+}
- document.getElementById('searchButton').addEventListener('click', searchFiles);
- document.getElementById('newFolderBtn').addEventListener('click', showNewFolderModal);
+// =================== MENU CONTEXTUEL ===================
+function initializeContextMenu() {
+ const contextMenu = document.querySelector('.context-menu');
+ let selectedItem = null;
- document.querySelectorAll('.delete-folder-button').forEach(button => {
- button.addEventListener('click', function() {
- const folderName = this.getAttribute('data-folder-name');
- confirmDeleteFolder(folderName);
- });
- });
+ // Gestionnaire du clic droit
+ document.addEventListener('contextmenu', function(e) {
+ const row = e.target.closest('tr[data-type]');
+ if (!row) return;
+
+ e.preventDefault();
+
+ selectedItem = {
+ type: row.dataset.type,
+ name: row.dataset.name,
+ url: row.dataset.url,
+ element: row
+ };
- document.querySelectorAll('.delete-file-button').forEach(button => {
- button.addEventListener('click', function() {
- const fileName = this.getAttribute('data-file-name');
- confirmDelete(fileName);
- });
- });
+ adjustMenuOptions(selectedItem);
+ showContextMenu(e.pageX, e.pageY);
+ });
- document.querySelectorAll('.copy-button').forEach(button => {
- button.addEventListener('click', function() {
- const fileUrl = this.getAttribute('data-file-url');
- copyFileLink(fileUrl);
- });
- });
-
- document.querySelectorAll('.rename-file-btn').forEach(button => {
- button.addEventListener('click', function() {
- const fileName = this.getAttribute('data-file-name');
- const folderName = this.getAttribute('data-folder-name');
- renameFile(folderName, fileName);
- });
- });
-
- document.querySelectorAll('.move-file-btn').forEach(button => {
- button.addEventListener('click', function() {
- const fileName = this.getAttribute('data-file-name');
- showMoveFileModal(fileName);
- });
- });
-
- document.getElementById('confirmMoveFile').addEventListener('click', moveFile);
- document.getElementById('themeSwitcher').addEventListener('click', toggleDarkMode);
-
- initTheme();
-
- document.addEventListener('DOMContentLoaded', function () {
- const accountDropdownBtn = document.getElementById('accountDropdownBtn');
- const accountDropdownMenu = document.getElementById('accountDropdownMenu');
-
- accountDropdownBtn.addEventListener('click', function (e) {
- e.stopPropagation();
- accountDropdownMenu.classList.toggle('show');
- });
-
- document.addEventListener('click', function (e) {
- if (!accountDropdownBtn.contains(e.target) && !accountDropdownMenu.contains(e.target)) {
- accountDropdownMenu.classList.remove('show');
- }
- });
- });
-
- $('.modal').modal({
- show: false
- });
-
- const metadataLink = document.querySelector('a[onclick="displayMetadata()"]');
- if (metadataLink) {
- metadataLink.addEventListener('click', function(event) {
- event.preventDefault();
- displayMetadata();
- });
+ // Fermer le menu au clic extérieur
+ document.addEventListener('click', function(e) {
+ if (!contextMenu?.contains(e.target)) {
+ hideContextMenu();
}
+ });
- document.querySelectorAll('[onclick^="showFileInfo"]').forEach(link => {
- link.addEventListener('click', function(event) {
- event.preventDefault();
- const fileName = this.getAttribute('onclick').match(/'([^']+)'/)[1];
- showFileInfo(fileName);
- });
+ // Actions du menu
+ document.querySelectorAll('.context-menu .menu-item').forEach(item => {
+ item.addEventListener('click', function(e) {
+ e.preventDefault();
+ const action = this.dataset.action;
+ if (selectedItem) {
+ handleMenuAction(action, selectedItem);
+ }
+ hideContextMenu();
});
});
- function formatFileSize(fileSizeInBytes) {
- if (fileSizeInBytes < 1024) return fileSizeInBytes + ' octets';
- else if (fileSizeInBytes < 1048576) return (fileSizeInBytes / 1024).toFixed(2) + ' Ko';
- else if (fileSizeInBytes < 1073741824) return (fileSizeInBytes / 1048576).toFixed(2) + ' Mo';
- else return (fileSizeInBytes / 1073741824).toFixed(2) + ' Go';
+ function adjustMenuOptions(item) {
+ const menuItems = contextMenu.querySelectorAll('.menu-item');
+
+ menuItems.forEach(menuItem => {
+ const action = menuItem.dataset.action;
+
+ switch(action) {
+ case 'open':
+ menuItem.style.display = item.type.includes('folder') ? 'flex' : 'none';
+ break;
+ case 'collaborate':
+ menuItem.style.display = item.type === 'folder' ? 'flex' : 'none';
+ const collabBtn = item.element.querySelector('.toggle-collaboration-btn');
+ const isCollaborative = collabBtn?.dataset.isCollaborative === 'true';
+ menuItem.querySelector('span').textContent =
+ isCollaborative ? 'Gérer la collaboration' : 'Activer la collaboration';
+ break;
+ case 'copy-link':
+ menuItem.style.display = item.type === 'file' ? 'flex' : 'none';
+ break;
+ }
+ });
}
- function searchFiles() {
- const input = document.getElementById('searchInput');
- const filter = input.value.toUpperCase();
- const table = document.getElementById('fileTable');
- const tr = table.getElementsByTagName('tr');
+ function showContextMenu(x, y) {
+ if (!contextMenu) return;
+
+ contextMenu.style.display = 'block';
+
+ // Ajuster la position
+ const menuRect = contextMenu.getBoundingClientRect();
+ const windowWidth = window.innerWidth;
+ const windowHeight = window.innerHeight;
- for (let i = 1; i < tr.length; i++) {
- const td = tr[i].getElementsByTagName('td')[0];
- if (td) {
- const txtValue = td.textContent || td.innerText;
- if (txtValue.toUpperCase().indexOf(filter) > -1) {
- tr[i].style.display = "";
- } else {
- tr[i].style.display = "none";
+ if (x + menuRect.width > windowWidth) {
+ x = windowWidth - menuRect.width - 10;
+ }
+ if (y + menuRect.height > windowHeight) {
+ y = windowHeight - menuRect.height - 10;
+ }
+
+ contextMenu.style.left = `${x}px`;
+ contextMenu.style.top = `${y}px`;
+ }
+
+ function hideContextMenu() {
+ if (contextMenu) {
+ contextMenu.style.display = 'none';
+ }
+ selectedItem = null;
+ }
+
+ function handleMenuAction(action, item) {
+ switch(action) {
+ case 'open':
+ if (item.type === 'folder') {
+ window.location.href = `/dpanel/dashboard/folder/${encodeURIComponent(item.name)}`;
+ } else if (item.type === 'shared-folder') {
+ window.location.href = item.url;
}
- }
+ break;
+
+ case 'rename':
+ if (item.type === 'folder') {
+ renameFolder(item.name);
+ } else if (item.type === 'file') {
+ renameFile(item.name);
+ }
+ break;
+
+ case 'collaborate':
+ const collabBtn = item.element.querySelector('.toggle-collaboration-btn');
+ const isCollaborative = collabBtn?.dataset.isCollaborative === 'true';
+
+ if (isCollaborative) {
+ showCollaborationDetails(item.name, item.type);
+ } else {
+ toggleCollaboration(item.name, item.type, true);
+ }
+ break;
+
+ case 'copy-link':
+ if (item.url) {
+ navigator.clipboard.writeText(item.url).then(() => {
+ showToast('success', 'Lien copié');
+ });
+ }
+ break;
+
+ case 'move':
+ if (item.type === 'file') {
+ // Utiliser la modal Bootstrap pour le déplacement
+ showMoveFileBootstrapModal(item.name);
+ }
+ break;
+
+ case 'delete':
+ if (item.type === 'folder') {
+ confirmDeleteFolder(item.name);
+ } else if (item.type === 'file') {
+ confirmDeleteFile(item.name);
+ }
+ break;
}
}
+}
- function showNewFolderModal() {
- Swal.fire({
- title: 'Nouveau dossier',
- input: 'text',
- inputPlaceholder: 'Entrer le nom du nouveau dossier',
- confirmButtonText: 'Créer',
- showCancelButton: true,
- cancelButtonText: 'Annuler',
- preConfirm: (folderName) => {
- if (!folderName) {
- Swal.showValidationMessage('Le nom du dossier ne peut pas être vide.');
+// =================== DROPDOWNS ===================
+function initializeDropdowns() {
+ // Gestionnaire pour les dropdowns Bootstrap
+ document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(toggle => {
+ toggle.addEventListener('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ // Fermer les autres dropdowns
+ document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
+ if (menu !== this.nextElementSibling) {
+ menu.classList.remove('show');
}
- return folderName;
- }
- }).then(result => {
- if (result.isConfirmed) {
- createNewFolder(result.value);
+ });
+
+ // Toggle le dropdown actuel
+ const dropdown = this.nextElementSibling;
+ if (dropdown && dropdown.classList.contains('dropdown-menu')) {
+ dropdown.classList.toggle('show');
}
});
- }
+ });
- function createNewFolder(folderName) {
- fetch('/api/dpanel/dashboard/newfolder', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ folderName }),
- })
- .then(response => {
- if (response.ok) {
- return response.json();
- } else {
- return response.json().then(error => Promise.reject(error));
- }
- })
- .then(result => {
- Swal.fire({
- position: 'top',
- icon: 'success',
- title: 'Le dossier a été créé avec succès.',
- showConfirmButton: false,
- timer: 2000,
- toast: true
- }).then(() => {
- location.reload();
- });
- })
- .catch(error => {
- Swal.fire({
- position: 'top',
- icon: 'error',
- title: 'Erreur lors de la création du dossier.',
- text: error.message,
- showConfirmButton: false,
- timer: 2350,
- toast: true
- });
+ // Fermer les dropdowns au clic extérieur
+ document.addEventListener('click', function(e) {
+ if (!e.target.closest('.dropdown')) {
+ document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
+ menu.classList.remove('show');
});
- }
+ }
+ });
- function confirmDeleteFolder(folderName) {
- Swal.fire({
- title: 'Êtes-vous sûr?',
- text: `La suppression du dossier "${folderName}" est irréversible!`,
- icon: 'warning',
- showCancelButton: true,
- confirmButtonColor: '#d33',
- cancelButtonColor: '#3085d6',
- confirmButtonText: 'Supprimer',
- cancelButtonText: 'Annuler',
- }).then((result) => {
- if (result.isConfirmed) {
- deleteFolder(folderName);
- }
+ // Empêcher la fermeture du dropdown quand on clique à l'intérieur
+ document.querySelectorAll('.dropdown-menu').forEach(menu => {
+ menu.addEventListener('click', function(e) {
+ e.stopPropagation();
});
- }
+ });
+}
- function deleteFolder(folderName) {
- fetch(`/api/dpanel/dashboard/deletefolder/${folderName}`, {
- method: 'DELETE',
- })
- .then(response => {
- if (response.ok) {
- Swal.fire({
- position: 'top',
- icon: 'success',
- title: 'Le dossier a été supprimé avec succès.',
- showConfirmButton: false,
- timer: 1800,
- toast: true
- }).then(() => {
- location.reload();
- });
- } else {
- throw new Error('La suppression du dossier a échoué');
- }
- })
- .catch(error => {
- Swal.fire({
- position: 'top',
- icon: 'error',
- title: error.message,
- showConfirmButton: false,
- timer: 1800,
- toast: true
+// =================== VUE GRILLE ===================
+function initializeGridView() {
+ const tableBody = document.querySelector('#fileTable tbody');
+ const searchContainer = document.querySelector('.flex.justify-between.items-center');
+
+ if (!tableBody || !searchContainer) return;
+
+ // Créer le bouton de basculement
+ const viewToggleBtn = document.createElement('button');
+ viewToggleBtn.className = 'btn btn-secondary ml-2 view-toggle-btn';
+ viewToggleBtn.innerHTML = '';
+ viewToggleBtn.title = 'Basculer vers la vue grille';
+ searchContainer.appendChild(viewToggleBtn);
+
+ let isGridView = localStorage.getItem('isGridView') === 'true'; function buildGridLayout(tr) {
+ if (!tr) return;
+
+ const firstCell = tr.querySelector('td:first-child');
+ if (!firstCell) return;
+
+ const nameDiv = firstCell.querySelector('div');
+ if (!nameDiv) return;
+
+ const name = nameDiv.textContent.trim();
+ const icon = firstCell.querySelector('i')?.cloneNode(true);
+ const type = tr.querySelector('td:nth-child(2)')?.textContent.trim() || '';
+ const size = tr.querySelector('td:nth-child(4)')?.textContent.trim() || '';
+ const collab = firstCell.querySelector('.collaboration-badge');
+
+ if (!icon) return;
+
+ // Créer le conteneur principal
+ const content = document.createElement('div');
+ content.className = 'icon-container';
+
+ // Créer l'icône avec arrière-plan stylé
+ const iconWrapper = document.createElement('div');
+ iconWrapper.className = 'icon';
+ iconWrapper.appendChild(icon);
+
+ // Créer le label
+ const label = document.createElement('div');
+ label.className = 'label';
+ label.textContent = name;
+
+ // Créer les détails
+ const details = document.createElement('div');
+ details.className = 'details';
+ details.textContent = size !== '-' ? `${type} • ${size}` : type;
+
+ // Assembler le conteneur
+ content.appendChild(iconWrapper);
+ content.appendChild(label);
+ content.appendChild(details);
+
+ // Nettoyer et remplacer le contenu
+ firstCell.innerHTML = '';
+ firstCell.appendChild(content);
+
+ // Réajouter le badge de collaboration s'il existe
+ if (collab) {
+ const clonedCollab = collab.cloneNode(true);
+ firstCell.appendChild(clonedCollab);
+ }
+
+ // Ajouter le dropdown pour la vue grille
+ const dropdown = tr.querySelector('.dropdown');
+ if (dropdown) {
+ const gridDropdown = dropdown.cloneNode(true);
+ gridDropdown.className = 'grid-dropdown dropdown';
+ firstCell.appendChild(gridDropdown);
+ }
+ } function toggleView() {
+ const currentIsGrid = tableBody.classList.contains('grid-view');
+ isGridView = !currentIsGrid;
+
+ if (isGridView) {
+ // Passer à la vue grille
+ tableBody.classList.add('grid-view');
+
+ // Appliquer le layout grille avec un délai pour l'animation
+ setTimeout(() => {
+ document.querySelectorAll('#fileTable tbody tr').forEach((tr, index) => {
+ tr.style.animationDelay = `${index * 0.05}s`;
+ buildGridLayout(tr);
});
- });
- }
-
- function confirmDelete(filename) {
- Swal.fire({
- title: 'Êtes-vous sûr de vouloir supprimer ce fichier?',
- text: 'Cette action est irréversible!',
- icon: 'warning',
- showCancelButton: true,
- confirmButtonColor: '#d33',
- cancelButtonColor: '#3085d6',
- confirmButtonText: 'Supprimer'
- }).then((result) => {
- if (result.isConfirmed) {
- deleteFile(filename);
- }
- });
- }
-
- function deleteFile(filename) {
- fetch('/api/dpanel/dashboard/delete', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- filename: filename,
- }),
- })
- .then(response => {
- if (response.ok) {
- Swal.fire({
- position: 'top',
- icon: 'success',
- title: 'Le fichier a été supprimé avec succès.',
- showConfirmButton: false,
- timer: 1800,
- toast: true
- }).then(() => {
- location.reload();
- });
- } else {
- throw new Error('La suppression du fichier a échoué');
- }
- })
- .catch(error => {
- Swal.fire({
- position: 'top',
- icon: 'error',
- title: error.message,
- showConfirmButton: false,
- timer: 1800,
- toast: true
- });
- });
- }
-
- function copyFileLink(fileUrl) {
- navigator.clipboard.writeText(fileUrl).then(() => {
- Swal.fire({
- position: 'top',
- icon: 'success',
- title: 'Lien copié !',
- showConfirmButton: false,
- timer: 1500,
- toast: true
- });
- }, (err) => {
- console.error('Erreur lors de la copie: ', err);
- });
- }
-
- function renameFile(folderName, currentName) {
- Swal.fire({
- title: 'Entrez le nouveau nom',
- input: 'text',
- inputValue: currentName,
- inputPlaceholder: 'Nouveau nom',
- showCancelButton: true,
- confirmButtonText: 'Renommer',
- cancelButtonText: 'Annuler',
- inputValidator: (value) => {
- if (!value) {
- return 'Vous devez entrer un nom de fichier';
- }
- }
- }).then((result) => {
- if (result.isConfirmed) {
- const newName = result.value;
- fetch(`/api/dpanel/dashboard/rename/${folderName}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ currentName: currentName, newName: newName }),
- })
- .then(response => {
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
- return response.json();
- })
- .then(data => {
- Swal.fire({
- position: 'top',
- icon: 'success',
- title: 'Le fichier a été renommé avec succès.',
- showConfirmButton: false,
- timer: 1800,
- toast: true,
- }).then(() => {
- location.reload();
- });
- })
- .catch((error) => {
- Swal.fire({
- position: 'top',
- icon: 'error',
- title: 'Erreur lors du renommage du fichier.',
- text: error.message,
- showConfirmButton: false,
- timer: 1800,
- toast: true,
- });
- });
- }
- });
- }
-
- function showMoveFileModal(fileName) {
- document.getElementById('moveFileName').value = fileName;
- $('#moveFileModal').modal('show');
- }
-
- function moveFile() {
- const fileName = document.getElementById('moveFileName').value;
- const folderName = document.getElementById('moveFolderSelect').value;
-
- fetch('/api/dpanel/dashboard/movefile', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ fileName: fileName, folderName: folderName }),
- })
- .then(response => {
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
- return response.json();
- })
- .then(data => {
- if (data.message === "File moved successfully") {
- Swal.fire({
- position: 'top',
- icon: 'success',
- title: 'Le fichier a été déplacé avec succès.',
- showConfirmButton: false,
- timer: 1800,
- toast: true,
- }).then(() => {
- location.reload();
- });
- } else {
- throw new Error(data.error || 'Une erreur est survenue');
- }
- })
- .catch((error) => {
- Swal.fire({
- position: 'top',
- icon: 'error',
- title: 'Erreur lors du déplacement du fichier.',
- text: error.message,
- showConfirmButton: false,
- timer: 1800,
- toast: true,
- });
- });
-
- $('#moveFileModal').modal('hide');
- }
-
- const body = document.body;
- const themeSwitcher = document.getElementById('themeSwitcher');
-
- function setTheme(theme) {
- if (theme === 'dark') {
- body.classList.add('dark');
+ }, 50);
+
+ viewToggleBtn.innerHTML = '';
+ viewToggleBtn.title = 'Basculer vers la vue liste';
} else {
- body.classList.remove('dark');
+ // Retourner à la vue liste
+ tableBody.classList.remove('grid-view');
+
+ // Rafraîchir pour restaurer la vue liste originale
+ setTimeout(() => location.reload(), 100);
}
- localStorage.setItem('theme', theme);
+
+ localStorage.setItem('isGridView', isGridView);
}
+ viewToggleBtn.addEventListener('click', toggleView); // Initialiser la vue si nécessaire
+ if (isGridView) {
+ tableBody.classList.add('grid-view');
+
+ // Appliquer le layout avec animation délayée
+ setTimeout(() => {
+ document.querySelectorAll('#fileTable tbody tr').forEach((tr, index) => {
+ tr.style.animationDelay = `${index * 0.05}s`;
+ buildGridLayout(tr);
+ });
+ }, 100);
+
+ viewToggleBtn.innerHTML = '';
+ viewToggleBtn.title = 'Basculer vers la vue liste';
+ }
+}
+
+// =================== COLLABORATION ===================
+function initializeCollaboration() {
+ // WebSocket pour la collaboration en temps réel
+ try {
+ const ws = new WebSocket(`ws://${window.location.host}`);
+
+ ws.onmessage = function(event) {
+ const data = JSON.parse(event.data);
+ if (data.type === 'collaborationStatus') {
+ updateCollaborationUI(data);
+ }
+ };
+
+ ws.onerror = function(error) {
+ console.warn('WebSocket error:', error);
+ };
+ } catch (error) {
+ console.warn('WebSocket not available:', error);
+ }
+
+ // Charger le statut initial
+ loadCollaborationStatus();
+
+ // Gestionnaires d'événements
+ document.querySelectorAll('.toggle-collaboration-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const itemName = this.getAttribute('data-item-name');
+ const itemType = this.getAttribute('data-item-type');
+ const isCollaborative = this.getAttribute('data-is-collaborative') === 'true';
+
+ if (isCollaborative) {
+ showCollaborationDetails(itemName, itemType);
+ } else {
+ toggleCollaboration(itemName, itemType, true);
+ }
+ });
+ });
+}
+
+async function loadCollaborationStatus() {
+ try {
+ const response = await fetch('/api/dpanel/collaboration/status');
+ const data = await response.json();
+
+ if (data.items) {
+ Object.entries(data.items).forEach(([itemId, status]) => {
+ const [type, name] = itemId.split('-');
+ updateCollaborationUI({
+ itemName: name,
+ itemType: type,
+ isCollaborative: status.isCollaborative,
+ activeUsers: status.activeUsers
+ });
+ });
+ }
+ } catch (error) {
+ console.error('Error loading collaboration status:', error);
+ }
+}
+
+function toggleCollaboration(itemName, itemType, enable) {
+ const itemLabel = itemType === 'folder' ? 'dossier' : 'fichier';
+
+ const performToggle = () => {
+ fetch('/api/dpanel/collaboration/toggle', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ itemName, itemType, enable })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ updateCollaborationUI({
+ itemName,
+ itemType,
+ isCollaborative: enable,
+ activeUsers: data.activeUsers || []
+ });
+ showToast('success', `Collaboration ${enable ? 'activée' : 'désactivée'}`);
+ } else {
+ throw new Error(data.error || 'Erreur inconnue');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ showToast('error', 'Erreur lors de la modification de la collaboration');
+ });
+ };
+
+ if (!enable) {
+ performToggle();
+ } else {
+ Swal.fire({
+ title: 'Activer la collaboration ?',
+ text: `D'autres utilisateurs pourront accéder à ce ${itemLabel} en temps réel.`,
+ icon: 'question',
+ showCancelButton: true,
+ confirmButtonText: 'Activer',
+ cancelButtonText: 'Annuler'
+ }).then((result) => {
+ if (result.isConfirmed) {
+ performToggle();
+ }
+ });
+ }
+}
+
+function showCollaborationDetails(itemName, itemType) {
+ fetch(`/api/dpanel/collaboration/details/${itemType}/${itemName}`)
+ .then(response => response.json())
+ .then(data => {
+ // Supprimer toute modal existante
+ document.querySelectorAll('.collaboration-modal').forEach(modal => modal.remove());
+
+ const modal = createCollaborationModal(itemName, itemType, data);
+ document.body.appendChild(modal);
+
+ // Afficher la modal avec les classes CSS modernes
+ modal.classList.add('show');
+ modal.style.display = 'flex';
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ showToast('error', 'Impossible de charger les détails de la collaboration');
+ });
+}
+
+function createCollaborationModal(itemName, itemType, data) {
+ const modal = document.createElement('div');
+ modal.className = 'collaboration-modal';
+ modal.style.display = 'flex';
+ modal.innerHTML = `
+
+
+
+
+
+
+
+ ${data.activeUsers && data.activeUsers.length > 0
+ ? data.activeUsers.map(user => `
+
+
+
})
+
+
+
+
${user.name}
+
+
+ Collaborateur
+
+
+
+
+
+
+ `).join('')
+ : `
+
+
Aucun collaborateur actif
+
Ajoutez des utilisateurs pour commencer à collaborer
+
`
+ }
+
+
+
+
+
+
+
+
+ `;
+
+ // Gestionnaire pour fermer en cliquant à l'extérieur
+ modal.addEventListener('click', (e) => {
+ if (e.target === modal) {
+ modal.remove();
+ }
+ });
+
+ // Ajouter les gestionnaires d'événements
+ const searchInput = modal.querySelector('.search-user-input');
+ const searchBtn = modal.querySelector('.search-user-btn');
+
+ const search = () => searchCollabUser(searchInput.value.trim(), itemName, itemType, modal);
+
+ searchBtn.addEventListener('click', search);
+ searchInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') search();
+ });
+
+ return modal;
+}
+
+function searchCollabUser(username, itemName, itemType, modal) {
+ if (!username) return;
+
+ const resultsDiv = modal.querySelector('.search-results');
+ resultsDiv.innerHTML = `
+
+
+ Recherche en cours...
+
+ `;
+
+ fetch(`/api/dpanel/collaboration/searchuser?username=${encodeURIComponent(username)}`)
+ .then(response => response.json())
+ .then(result => {
+ if (result.found) {
+ resultsDiv.innerHTML = `
+
+
+
+
})
+
+
+
${result.user.name}
+
+
+ Utilisateur trouvé
+
+
+
+
+
+ `;
+ } else {
+ resultsDiv.innerHTML = `
+
+
+ Utilisateur non trouvé
+
+ `;
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ resultsDiv.innerHTML = `
+
+
+ Erreur lors de la recherche
+
+ `;
+ });
+}
+
+function addCollaborator(itemName, itemType, userId) {
+ fetch('/api/dpanel/collaboration/add', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ itemName, itemType, userId })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showToast('success', 'Collaborateur ajouté');
+ // Rafraîchir la modal
+ document.querySelector('.collaboration-modal')?.remove();
+ showCollaborationDetails(itemName, itemType);
+ } else {
+ throw new Error(data.error || 'Erreur lors de l\'ajout');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ showToast('error', 'Erreur lors de l\'ajout du collaborateur');
+ });
+}
+
+function removeCollaborator(itemName, itemType, userId) {
+ fetch('/api/dpanel/collaboration/remove', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ itemName, itemType, userId })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showToast('success', 'Collaborateur retiré');
+ // Rafraîchir la modal
+ document.querySelector('.collaboration-modal')?.remove();
+ showCollaborationDetails(itemName, itemType);
+ } else {
+ throw new Error(data.error || 'Erreur lors du retrait');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ showToast('error', 'Erreur lors du retrait du collaborateur');
+ });
+}
+
+function updateCollaborationUI(data) {
+ const row = document.querySelector(`tr[data-name="${data.itemName}"]`);
+ if (!row) return;
+
+ const button = row.querySelector('.toggle-collaboration-btn');
+ const nameCell = row.querySelector('td:first-child');
+
+ if (button) {
+ button.setAttribute('data-is-collaborative', data.isCollaborative);
+ button.title = data.isCollaborative ? 'Voir les collaborateurs' : 'Activer la collaboration';
+ }
+
+ let badge = nameCell.querySelector('.collaboration-badge');
+ if (data.isCollaborative) {
+ if (!badge) {
+ badge = document.createElement('span');
+ badge.className = 'ml-2 badge badge-info collaboration-badge';
+ badge.innerHTML = ' ';
+ nameCell.querySelector('div').appendChild(badge);
+ }
+
+ const countSpan = badge.querySelector('.active-users-count');
+ if (data.activeUsers && data.activeUsers.length > 0) {
+ badge.classList.remove('badge-secondary');
+ badge.classList.add('badge-info');
+ countSpan.textContent = ` ${data.activeUsers.length}`;
+ badge.title = `Collaborateurs actifs : ${data.activeUsers.map(u => u.name).join(', ')}`;
+ } else {
+ badge.classList.remove('badge-info');
+ badge.classList.add('badge-secondary');
+ countSpan.textContent = '';
+ badge.title = 'Aucun collaborateur actif';
+ }
+ } else if (badge) {
+ badge.remove();
+ }
+}
+
+// =================== GESTIONNAIRES DE FICHIERS ===================
+function initializeFileHandlers() {
+ // Boutons de suppression
+ document.querySelectorAll('.delete-file-button, .delete-folder-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const fileName = this.dataset.fileName;
+ const folderName = this.dataset.folderName;
+
+ if (fileName) {
+ confirmDeleteFile(fileName);
+ } else if (folderName) {
+ confirmDeleteFolder(folderName);
+ }
+ });
+ });
+
+ // Boutons de copie
+ document.querySelectorAll('.copy-button').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const fileUrl = this.dataset.fileUrl;
+ copyFileLink(fileUrl);
+ });
+ });
+
+ // Boutons de renommage
+ document.querySelectorAll('.rename-file-btn, .rename-folder-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const fileName = this.dataset.fileName;
+ const folderName = this.dataset.folderName;
+
+ if (fileName) {
+ renameFile(fileName);
+ } else if (folderName) {
+ renameFolder(folderName);
+ }
+ });
+ });
+
+ // Boutons de déplacement
+ document.querySelectorAll('.move-file-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const fileName = this.dataset.fileName;
+ // Utiliser la modal Bootstrap pour le déplacement
+ showMoveFileBootstrapModal(fileName);
+ });
+ }); // Gestionnaire pour le bouton confirmMoveFile dans la modal Bootstrap
+ const confirmMoveFileBtn = document.getElementById('confirmMoveFile');
+ if (confirmMoveFileBtn) {
+ confirmMoveFileBtn.addEventListener('click', function(e) {
+ e.preventDefault();
+
+ const fileNameElement = document.getElementById('moveFileName');
+ const folderSelectElement = document.getElementById('moveFolderSelect');
+
+ console.log('Elements found:', {
+ fileNameElement: !!fileNameElement,
+ folderSelectElement: !!folderSelectElement,
+ fileNameElementType: fileNameElement?.tagName,
+ folderSelectElementType: folderSelectElement?.tagName
+ });
+
+ if (!fileNameElement) {
+ showToast('error', 'Élément fileName non trouvé');
+ return;
+ }
+
+ if (!folderSelectElement) {
+ showToast('error', 'Élément folderSelect non trouvé');
+ return;
+ }
+ // Récupérer les valeurs brutes
+ const rawFileName = fileNameElement.value;
+ const rawFolderName = folderSelectElement.value;
+
+ console.log('Raw values:', {
+ rawFileName: rawFileName,
+ rawFolderName: rawFolderName,
+ rawFileNameType: typeof rawFileName,
+ rawFolderNameType: typeof rawFolderName,
+ fileNameIsString: typeof rawFileName === 'string',
+ folderNameIsString: typeof rawFolderName === 'string'
+ });
+
+ // Convertir explicitement en string et nettoyer
+ // Utiliser une méthode plus robuste de conversion
+ let fileName, folderName;
+
+ try {
+ fileName = (rawFileName + '').trim(); // Force conversion to string
+ folderName = (rawFolderName + '').trim();
+ } catch (e) {
+ console.error('Error converting values:', e);
+ fileName = String(rawFileName || '').trim();
+ folderName = String(rawFolderName || '').trim();
+ }
+
+ console.log('Processed values:', {
+ fileName: fileName,
+ folderName: folderName,
+ fileNameType: typeof fileName,
+ folderNameType: typeof folderName,
+ fileNameLength: fileName.length,
+ folderNameLength: folderName.length,
+ fileNameConstructor: fileName.constructor.name,
+ folderNameConstructor: folderName.constructor.name
+ });
+
+ if (!fileName || fileName === '') {
+ showToast('error', 'Aucun fichier sélectionné');
+ return;
+ }
+
+ if (!folderName || folderName === '') {
+ showToast('error', 'Veuillez sélectionner un dossier');
+ return;
+ }
+
+ // Fermer la modal
+ $('#moveFileModal').modal('hide');
+
+ // Appeler la fonction moveFile
+ console.log('Calling moveFile with:', { fileName, folderName });
+ moveFile(fileName, folderName);
+ });
+ }
+}
+
+// =================== FONCTIONS UTILITAIRES ===================
+function formatFileSize(fileSizeInBytes) {
+ if (!fileSizeInBytes || fileSizeInBytes === 0) return '0 octets';
+ if (fileSizeInBytes < 1024) return fileSizeInBytes + ' octets';
+ else if (fileSizeInBytes < 1048576) return (fileSizeInBytes / 1024).toFixed(2) + ' Ko';
+ else if (fileSizeInBytes < 1073741824) return (fileSizeInBytes / 1048576).toFixed(2) + ' Mo';
+ else return (fileSizeInBytes / 1073741824).toFixed(2) + ' Go';
+}
+
+function getDefaultAvatar(username) {
+ const encodedName = encodeURIComponent(username);
+ return `https://api.dicebear.com/7.x/initials/svg?seed=${encodedName}&background=%234e54c8&radius=50`;
+}
+
+function showToast(type, message) {
+ Swal.fire({
+ position: 'top-end',
+ icon: type,
+ title: message,
+ showConfirmButton: false,
+ timer: 1500,
+ toast: true
+ });
+}
+
+function searchFiles() {
+ const input = document.getElementById('searchInput');
+ const filter = input.value.toUpperCase();
+ const table = document.getElementById('fileTable');
+ const tr = table.getElementsByTagName('tr');
+
+ for (let i = 1; i < tr.length; i++) {
+ const td = tr[i].getElementsByTagName('td')[0];
+ if (td) {
+ const txtValue = td.textContent || td.innerText;
+ if (txtValue.toUpperCase().indexOf(filter) > -1) {
+ tr[i].style.display = "";
+ } else {
+ tr[i].style.display = "none";
+ }
+ }
+ }
+}
+
+// =================== MODALES ET ACTIONS ===================
+function showNewFolderModal() {
+ Swal.fire({
+ title: 'Nouveau dossier',
+ input: 'text',
+ inputPlaceholder: 'Entrer le nom du nouveau dossier',
+ confirmButtonText: 'Créer',
+ showCancelButton: true,
+ cancelButtonText: 'Annuler',
+ preConfirm: (folderName) => {
+ if (!folderName) {
+ Swal.showValidationMessage('Le nom du dossier ne peut pas être vide.');
+ }
+ return folderName;
+ }
+ }).then(result => {
+ if (result.isConfirmed) {
+ createNewFolder(result.value);
+ }
+ });
+}
+
+function createNewFolder(folderName) {
+ fetch('/api/dpanel/dashboard/newfolder', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ folderName })
+ })
+ .then(response => response.json())
+ .then(result => {
+ if (result.success || response.ok) {
+ showToast('success', 'Dossier créé avec succès');
+ setTimeout(() => location.reload(), 1500);
+ } else {
+ throw new Error(result.message || 'Erreur lors de la création');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ showToast('error', 'Erreur lors de la création du dossier');
+ });
+}
+
+function confirmDeleteFolder(folderName) {
+ Swal.fire({
+ title: 'Êtes-vous sûr?',
+ text: `La suppression du dossier "${folderName}" est irréversible!`,
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#d33',
+ cancelButtonColor: '#3085d6',
+ confirmButtonText: 'Supprimer',
+ cancelButtonText: 'Annuler',
+ }).then((result) => {
+ if (result.isConfirmed) {
+ deleteFolder(folderName);
+ }
+ });
+}
+
+function deleteFolder(folderName) {
+ fetch(`/api/dpanel/dashboard/deletefolder/${folderName}`, {
+ method: 'DELETE'
+ })
+ .then(response => {
+ if (response.ok) {
+ showToast('success', 'Dossier supprimé avec succès');
+ setTimeout(() => location.reload(), 1500);
+ } else {
+ throw new Error('La suppression du dossier a échoué');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ showToast('error', 'Erreur lors de la suppression du dossier');
+ });
+}
+
+function confirmDeleteFile(filename) {
+ Swal.fire({
+ title: 'Êtes-vous sûr de vouloir supprimer ce fichier?',
+ text: 'Cette action est irréversible!',
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#d33',
+ cancelButtonColor: '#3085d6',
+ confirmButtonText: 'Supprimer'
+ }).then((result) => {
+ if (result.isConfirmed) {
+ deleteFile(filename);
+ }
+ });
+}
+
+function deleteFile(filename) {
+ fetch('/api/dpanel/dashboard/delete', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ filename: filename })
+ })
+ .then(response => {
+ if (response.ok) {
+ showToast('success', 'Fichier supprimé avec succès');
+ setTimeout(() => location.reload(), 1500);
+ } else {
+ throw new Error('La suppression du fichier a échoué');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ showToast('error', 'Erreur lors de la suppression du fichier');
+ });
+}
+
+function copyFileLink(fileUrl) {
+ navigator.clipboard.writeText(fileUrl).then(() => {
+ showToast('success', 'Lien copié !');
+ }, (err) => {
+ console.error('Erreur lors de la copie: ', err);
+ showToast('error', 'Erreur lors de la copie');
+ });
+}
+
+function renameFile(currentName) {
+ Swal.fire({
+ title: 'Renommer le fichier',
+ input: 'text',
+ inputValue: currentName,
+ inputPlaceholder: 'Nouveau nom',
+ showCancelButton: true,
+ confirmButtonText: 'Renommer',
+ cancelButtonText: 'Annuler',
+ inputValidator: (value) => {
+ if (!value) {
+ return 'Vous devez entrer un nom de fichier';
+ }
+ }
+ }).then((result) => {
+ if (result.isConfirmed) {
+ const newName = result.value;
+ fetch(`/api/dpanel/dashboard/rename/`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ currentName: currentName, newName: newName })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success || response.ok) {
+ showToast('success', 'Fichier renommé avec succès');
+ setTimeout(() => location.reload(), 1500);
+ } else {
+ throw new Error(data.message || 'Erreur lors du renommage');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ showToast('error', 'Erreur lors du renommage du fichier');
+ });
+ }
+ });
+}
+
+function renameFolder(currentName) {
+ Swal.fire({
+ title: 'Renommer le dossier',
+ input: 'text',
+ inputValue: currentName,
+ showCancelButton: true,
+ confirmButtonText: 'Renommer',
+ cancelButtonText: 'Annuler',
+ inputValidator: (value) => {
+ if (!value) {
+ return 'Veuillez entrer un nom de dossier';
+ }
+ }
+ }).then((result) => {
+ if (result.isConfirmed) {
+ const newName = result.value;
+ fetch('/api/dpanel/folders/rename', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ oldName: currentName, newName: newName })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showToast('success', 'Dossier renommé avec succès');
+ setTimeout(() => location.reload(), 1500);
+ } else {
+ throw new Error(data.message || 'Erreur lors du renommage');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ showToast('error', 'Erreur lors du renommage du dossier');
+ });
+ }
+ });
+}
+
+function showMoveFileModal(fileName) {
+ const folders = Array.from(document.querySelectorAll('tr[data-type="folder"]'))
+ .map(folderRow => ({
+ name: folderRow.dataset.name,
+ value: folderRow.dataset.name
+ }));
+
+ Swal.fire({
+ title: 'Déplacer le fichier',
+ html: `
+
+ `,
+ showCancelButton: true,
+ confirmButtonText: 'Déplacer',
+ cancelButtonText: 'Annuler',
+ preConfirm: () => {
+ const select = document.getElementById('moveFolderSelect');
+ const folderName = select.value;
+ if (!folderName) {
+ Swal.showValidationMessage('Veuillez sélectionner un dossier');
+ return false;
+ }
+ return folderName;
+ }
+ }).then((result) => {
+ if (result.isConfirmed && result.value) {
+ moveFile(fileName, result.value);
+ }
+ });
+}
+
+// Fonction alternative utilisant la modal Bootstrap
+function showMoveFileBootstrapModal(fileName) {
+ // S'assurer que fileName est une chaîne de caractères
+ const fileNameStr = String(fileName).trim();
+
+ console.log('showMoveFileBootstrapModal called with:', {
+ original: fileName,
+ converted: fileNameStr,
+ type: typeof fileName
+ });
+
+ if (!fileNameStr) {
+ showToast('error', 'Nom de fichier invalide');
+ return;
+ }
+
+ // Définir le nom du fichier dans le champ caché
+ const moveFileNameElement = document.getElementById('moveFileName');
+ if (moveFileNameElement) {
+ moveFileNameElement.value = fileNameStr;
+ console.log('Set moveFileName value to:', moveFileNameElement.value);
+ } else {
+ console.error('Element moveFileName not found');
+ showToast('error', 'Erreur: Élément de formulaire manquant');
+ return;
+ }
+
+ // Afficher la modal Bootstrap
+ $('#moveFileModal').modal('show');
+}
+
+function moveFile(fileName, folderName) {
+ // S'assurer que les paramètres sont des chaînes de caractères
+ const fileNameStr = String(fileName).trim();
+ const folderNameStr = String(folderName).trim();
+
+ console.log('moveFile called with:', {
+ original: { fileName, folderName },
+ converted: { fileNameStr, folderNameStr },
+ types: {
+ originalFileName: typeof fileName,
+ originalFolderName: typeof folderName,
+ convertedFileName: typeof fileNameStr,
+ convertedFolderName: typeof folderNameStr
+ }
+ });
+
+ if (!fileNameStr) {
+ showToast('error', 'Nom de fichier invalide');
+ return;
+ }
+
+ if (!folderNameStr) {
+ showToast('error', 'Nom de dossier invalide');
+ return;
+ }
+ // Créer explicitement l'objet à envoyer
+ const requestBody = {};
+ requestBody.fileName = fileNameStr;
+ requestBody.folderName = folderNameStr;
+
+ // Vérification finale
+ console.log('Request body before sending:', {
+ requestBody: requestBody,
+ stringify: JSON.stringify(requestBody),
+ fileNameInBody: requestBody.fileName,
+ folderNameInBody: requestBody.folderName,
+ fileNameType: typeof requestBody.fileName,
+ folderNameType: typeof requestBody.folderName,
+ fileNameConstructor: requestBody.fileName.constructor.name,
+ folderNameConstructor: requestBody.folderName.constructor.name
+ });
+
+ const jsonPayload = JSON.stringify(requestBody);
+ console.log('JSON payload:', jsonPayload);
+
+ fetch('/api/dpanel/dashboard/movefile', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: jsonPayload
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.message === "File moved successfully" || data.success) {
+ showToast('success', 'Fichier déplacé avec succès');
+ setTimeout(() => location.reload(), 1500);
+ } else {
+ throw new Error(data.error || 'Une erreur est survenue');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ showToast('error', 'Erreur lors du déplacement du fichier');
+ });
+}
+
+// =================== THÈME ===================
+function initTheme() {
+ const body = document.body;
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');
- }
- });
-
- async function showFileInfo(fileName) {
- try {
- const response = await fetch('/api/dpanel/dashboard/getmetadatafile/file_info', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- fileLink: fileName,
- })
- });
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- const data = await response.json();
- const fileInfo = data.find(file => file.fileName === fileName);
-
- if (!fileInfo) {
- throw new Error(`No information found for the file ${fileName}.`);
- }
-
- let html = `Nom du fichier : ${fileInfo.fileName}
`;
- if (fileInfo.expiryDate) {
- html += `Date d'expiration : ${fileInfo.expiryDate}
`;
- }
- if (fileInfo.password) {
- html += `Mot de passe : Oui
`;
- }
- if (fileInfo.userId) {
- html += `Utilisateur : ${fileInfo.userId}
`;
- }
-
- Swal.fire({
- title: 'Informations sur le fichier',
- html: html,
- confirmButtonText: 'Fermer'
- });
- } catch (error) {
- console.error('Error in showFileInfo:', error);
- Swal.fire({
- position: 'top',
- icon: 'error',
- title: 'Les informations sur le fichier ne sont pas disponibles pour le moment.',
- text: `Erreur : ${error.message}`,
- showConfirmButton: false,
- timer: 1800,
- toast: true,
- });
- }
- }
-
- function displayMetadata() {
- fetch('/build-metadata')
- .then(response => response.json())
- .then(metadata => {
- document.getElementById('buildVersion').textContent = metadata.build_version;
- document.getElementById('nodeVersion').textContent = metadata.node_version;
- document.getElementById('expressVersion').textContent = metadata.express_version;
- document.getElementById('buildSha').textContent = metadata.build_sha;
- document.getElementById('osType').textContent = metadata.os_type;
- document.getElementById('osRelease').textContent = metadata.os_release;
-
- $('#metadataModal').modal('show');
- })
- .catch(error => {
- console.error('Failed to fetch metadata:', error);
- Swal.fire({
- icon: 'error',
- title: 'Erreur',
- text: 'Impossible de récupérer les métadonnées'
- });
- });
- }
- document.addEventListener('DOMContentLoaded', () => {
- // Recherche avec debounce
- const searchInput = document.getElementById('searchInput');
- if (searchInput) {
- let timeout;
- searchInput.addEventListener('input', (e) => {
- clearTimeout(timeout);
- timeout = setTimeout(() => {
- const term = e.target.value.toLowerCase();
- document.querySelectorAll('#fileTable tbody tr').forEach(row => {
- const text = row.querySelector('td:first-child').textContent.toLowerCase();
- row.style.display = text.includes(term) ? '' : 'none';
- });
- }, 150);
- });
- }
-
- // Gestion des modales
- document.querySelectorAll('[data-toggle="modal"]').forEach(trigger => {
- trigger.addEventListener('click', () => {
- const modal = document.querySelector(trigger.dataset.target);
- if (modal) modal.classList.add('show');
- });
- });
-
- document.querySelectorAll('.modal .close, .modal .btn-secondary').forEach(btn => {
- btn.addEventListener('click', () => {
- const modal = btn.closest('.modal');
- if (modal) modal.classList.remove('show');
- });
- });
-
- // Dropdowns
- document.querySelectorAll('.dropdown-toggle').forEach(toggle => {
- toggle.addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- const menu = toggle.nextElementSibling;
- if (!menu) return;
-
- // Fermer les autres dropdowns
- document.querySelectorAll('.dropdown-menu.show').forEach(m => {
- if (m !== menu) m.classList.remove('show');
- });
-
- menu.classList.toggle('show');
- });
- });
-
- // Fermer les dropdowns au clic extérieur
- document.addEventListener('click', () => {
- document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
- menu.classList.remove('show');
- });
- });
- });
-
- // Loading overlay
- window.showLoadingState = () => {
- const overlay = document.createElement('div');
- overlay.className = 'loading-overlay animate';
- overlay.innerHTML = '';
- document.body.appendChild(overlay);
- };
-
- window.hideLoadingState = () => {
- const overlay = document.querySelector('.loading-overlay');
- if (overlay) overlay.remove();
- };
-
- // Gestion améliorée de l'état de chargement
-window.showLoadingState = () => {
- const overlay = document.createElement('div');
- overlay.className = 'loading-overlay';
-
- const wrapper = document.createElement('div');
- wrapper.className = 'spinner-wrapper';
-
- const spinner = document.createElement('div');
- spinner.className = 'loading-spinner';
-
- wrapper.appendChild(spinner);
- overlay.appendChild(wrapper);
- document.body.appendChild(overlay);
-
- // Force le reflow pour démarrer l'animation
- overlay.offsetHeight;
- overlay.classList.add('show');
-};
-
-window.hideLoadingState = () => {
- const overlay = document.querySelector('.loading-overlay');
- if (overlay) {
- overlay.classList.remove('show');
- overlay.addEventListener('transitionend', () => overlay.remove(), { once: true });
- }
-};
-
-// Animation des lignes du tableau
-document.addEventListener('DOMContentLoaded', () => {
- const tableRows = document.querySelectorAll('.table tr');
- tableRows.forEach((row, index) => {
- row.style.setProperty('--row-index', index);
- requestAnimationFrame(() => row.classList.add('show'));
- });
-
- // Animation du conteneur principal
- const mainContainer = document.querySelector('.form-container');
- if (mainContainer) {
- requestAnimationFrame(() => mainContainer.classList.add('show'));
- }
-});
-
-// Fonction pour les transitions de page
-function transitionToPage(url) {
- document.body.classList.add('page-transition');
- showLoadingState();
-
- setTimeout(() => {
- window.location.href = url;
- }, 300);
}
-// Amélioration des modales
-function showModal(modalId) {
- const modal = document.querySelector(modalId);
- if (!modal) return;
-
- modal.style.display = 'flex';
- requestAnimationFrame(() => {
- modal.classList.add('show');
- modal.querySelector('.modal-content')?.classList.add('show');
- });
-}
-
-function hideModal(modalId) {
- const modal = document.querySelector(modalId);
- if (!modal) return;
-
- modal.querySelector('.modal-content')?.classList.remove('show');
- modal.classList.remove('show');
-
- modal.addEventListener('transitionend', () => {
- modal.style.display = 'none';
- }, { once: true });
-}
-
-// État de chargement des boutons
-function setButtonLoading(button, isLoading) {
- if (isLoading) {
- button.classList.add('loading');
- button.dataset.originalText = button.innerHTML;
- button.innerHTML = '';
+function setTheme(theme) {
+ const body = document.body;
+ if (theme === 'dark') {
+ body.classList.add('dark');
} else {
- button.classList.remove('loading');
- if (button.dataset.originalText) {
- button.innerHTML = button.dataset.originalText;
- }
+ body.classList.remove('dark');
+ }
+ localStorage.setItem('theme', theme);
+}
+
+function toggleDarkMode() {
+ const body = document.body;
+ if (body.classList.contains('dark')) {
+ setTheme('light');
+ } else {
+ setTheme('dark');
}
}
-function createLoadingScreen() {
- const container = document.createElement('div');
- container.className = 'initial-loading';
- const content = `
-
-
-
-
Vous y êtes presque !
-
Préparation de votre espace de travail...
-
Chargement des données
-
-
-
- `;
- container.innerHTML = content;
- document.body.appendChild(container);
- return container;
+// =================== VERSION ===================
+function loadVersion() {
+ fetch('/build-metadata')
+ .then(response => response.json())
+ .then(data => {
+ const versionElement = document.getElementById('version-number');
+ if (versionElement) {
+ versionElement.textContent = data.build_version;
+ }
+ })
+ .catch(error => {
+ console.error('Error fetching version:', error);
+ const versionElement = document.getElementById('version-number');
+ if (versionElement) {
+ versionElement.textContent = 'Version indisponible';
+ }
+ });
}
-function initializeLoadingScreen() {
- // Vérifier si c'est la première visite de la session
- const hasSeenAnimation = sessionStorage.getItem('hasSeenLoadingAnimation');
-
- if (hasSeenAnimation) {
- // Si l'animation a déjà été vue, initialiser directement le contenu
- const contentWrapper = document.querySelector('.content-wrapper');
- if (contentWrapper) {
- contentWrapper.classList.add('loaded');
- }
- return Promise.resolve();
- }
+// =================== METADATA ===================
+function displayMetadata() {
+ fetch('/build-metadata')
+ .then(response => response.json())
+ .then(metadata => {
+ document.getElementById('buildVersion').textContent = metadata.build_version;
+ document.getElementById('nodeVersion').textContent = metadata.node_version;
+ document.getElementById('expressVersion').textContent = metadata.express_version;
+ document.getElementById('buildSha').textContent = metadata.build_sha;
+ document.getElementById('osType').textContent = metadata.os_type;
+ document.getElementById('osRelease').textContent = metadata.os_release;
- return new Promise((resolve) => {
- const loadingScreen = createLoadingScreen();
-
- setTimeout(() => {
- loadingScreen.classList.add('fade-out');
- loadingScreen.addEventListener('animationend', () => {
- loadingScreen.remove();
- // Marquer l'animation comme vue pour cette session
- sessionStorage.setItem('hasSeenLoadingAnimation', 'true');
- resolve();
- }, { once: true });
- }, 2000);
- });
+ // Utiliser Bootstrap modal si disponible, sinon créer une modal simple
+ const modalElement = document.getElementById('metadataModal');
+ if (modalElement && typeof $ !== 'undefined' && $.fn.modal) {
+ $('#metadataModal').modal('show');
+ }
+ })
+ .catch(error => {
+ console.error('Failed to fetch metadata:', error);
+ showToast('error', 'Impossible de récupérer les métadonnées');
+ });
}
-document.addEventListener('DOMContentLoaded', async function() {
- try {
- await initializeLoadingScreen();
- const contentWrapper = document.querySelector('.content-wrapper');
- if (contentWrapper) {
- contentWrapper.classList.add('loaded');
- }
- } catch (error) {
- console.error('Erreur lors du chargement:', error);
- }
-});
-
-fetch('/build-metadata')
-.then(response => response.json())
-.then(data => {
- document.getElementById('version-number').textContent = data.build_version;
-})
-.catch(error => {
- console.error('Error fetching version:', error);
- document.getElementById('version-number').textContent = 'Version indisponible';
-});
\ No newline at end of file
+// Expose les fonctions globalement si nécessaire
+window.displayMetadata = displayMetadata;
+window.addCollaborator = addCollaborator;
+window.removeCollaborator = removeCollaborator;
+window.toggleCollaboration = toggleCollaboration;
diff --git a/public/js/profile.script.js b/public/js/profile.script.js
new file mode 100644
index 0000000..a21b73b
--- /dev/null
+++ b/public/js/profile.script.js
@@ -0,0 +1,268 @@
+document.addEventListener('DOMContentLoaded', function() {
+ initTheme();
+ initTabs();
+ initForms();
+ updateTokenDisplay();
+});
+
+// Gestion du thème
+function initTheme() {
+ const body = document.body;
+ const themeSwitcher = document.getElementById('themeSwitcher');
+
+ function setTheme(theme) {
+ if (theme === 'dark') {
+ body.classList.add('dark');
+ } else {
+ body.classList.remove('dark');
+ }
+ localStorage.setItem('theme', theme);
+ updateThemeIcon(theme);
+ }
+
+ function updateThemeIcon(theme) {
+ const icon = themeSwitcher.querySelector('i');
+ icon.className = theme === 'dark' ? 'fas fa-moon' : 'fas fa-sun';
+ }
+
+ const savedTheme = localStorage.getItem('theme');
+ if (savedTheme) {
+ setTheme(savedTheme);
+ } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ setTheme('dark');
+ }
+
+ themeSwitcher.addEventListener('click', () => {
+ setTheme(body.classList.contains('dark') ? 'light' : 'dark');
+ });
+}
+
+// Gestion des onglets
+function initTabs() {
+ const tabs = document.querySelectorAll('.tab');
+ tabs.forEach(tab => {
+ tab.addEventListener('click', () => switchTab(tab));
+ });
+}
+
+function switchTab(selectedTab) {
+ document.querySelectorAll('.tab, .tab-content').forEach(el => {
+ el.classList.remove('active');
+ });
+
+ selectedTab.classList.add('active');
+ const targetContent = document.querySelector(`[data-tab-content="${selectedTab.dataset.tab}"]`);
+ targetContent.classList.add('active');
+}
+
+// Gestion des formulaires et actions
+function initForms() {
+ const profileCustomizationForm = document.getElementById('profileCustomization');
+
+ if (profileCustomizationForm) {
+ profileCustomizationForm.addEventListener('submit', handleProfileCustomizationUpdate);
+ }
+}
+
+async function handleProfileCustomizationUpdate(e) {
+ e.preventDefault();
+ const wallpaperUrl = document.getElementById('wallpaperUrl').value;
+ const profilePictureUrl = document.getElementById('profilePictureUrl').value;
+
+ try {
+ let promises = []; // Mise à jour du fond d'écran si l'URL est fournie
+ if (wallpaperUrl && wallpaperUrl.trim()) {
+ promises.push(
+ fetch('/api/dpanel/dashboard/backgroundcustom/wallpaper', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ wallpaperUrl: wallpaperUrl
+ })
+ })
+ );
+ }
+
+ // Mise à jour de la photo de profil si l'URL est fournie
+ if (profilePictureUrl && profilePictureUrl.trim()) {
+ promises.push(
+ fetch('/api/dpanel/dashboard/profilpicture', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ profilePictureUrl: profilePictureUrl
+ })
+ })
+ );
+ }
+
+ if (promises.length === 0) {
+ showToast('error', 'Veuillez remplir au moins un champ');
+ return;
+ }
+
+ // Attendre toutes les requêtes
+ const responses = await Promise.all(promises);
+ // Vérifier toutes les réponses
+ let wallpaperData = null;
+ let profilePictureData = null;
+
+ for (let i = 0; i < responses.length; i++) {
+ const response = responses[i];
+
+ // Vérifier le type de contenu
+ const contentType = response.headers.get('content-type');
+ if (!contentType || !contentType.includes('application/json')) {
+ throw new Error('Le serveur a renvoyé une réponse non-JSON. Vérifiez votre authentification.');
+ }
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ throw new Error(data.error || `Erreur ${response.status}: ${response.statusText}`);
+ }
+
+ // Identifier quelle réponse correspond à quoi
+ if (data.wallpaper) {
+ wallpaperData = data;
+ } else if (data.profilePicture) {
+ profilePictureData = data;
+ }
+ }
+
+ // Appliquer les changements visuels
+ if (wallpaperData) {
+ document.body.style.backgroundImage = `url('${wallpaperData.wallpaper}')`;
+ }
+
+ if (profilePictureData) {
+ const profileImages = document.querySelectorAll('.avatar-container img, .profile-picture img, .user-avatar');
+ profileImages.forEach(img => {
+ img.src = profilePictureData.profilePicture;
+ });
+ }
+
+ showToast('success', 'Profil mis à jour avec succès');
+
+ // Vider les champs après la mise à jour réussie
+ document.getElementById('wallpaperUrl').value = '';
+ document.getElementById('profilePictureUrl').value = '';
+
+ } catch (error) {
+ showToast('error', error.message);
+ }
+}
+
+// Fonctions utilitaires
+function maskToken(token) {
+ if (!token || token === 'Aucun token') return 'Aucun token';
+ // Prendre les 8 premiers caractères et remplacer le reste par des points
+ return token.substring(0, 8) + '••••••••••••';
+}
+
+function updateTokenDisplay() {
+ const tokenDisplay = document.querySelector('.token-display code');
+ const fullToken = tokenDisplay.getAttribute('data-full-token');
+
+ if (!fullToken || fullToken === 'Aucun token') {
+ tokenDisplay.textContent = 'Aucun token';
+ } else {
+ // Afficher la version masquée mais conserver le token complet dans data-full-token
+ tokenDisplay.textContent = maskToken(fullToken);
+ }
+}
+
+function copyToken() {
+ const tokenElement = document.querySelector('.token-display code');
+ const fullToken = tokenElement.getAttribute('data-full-token'); // Récupère le token complet depuis l'attribut data-full-token
+
+ if (!fullToken || fullToken === 'Aucun token') {
+ showToast('error', 'Aucun token à copier');
+ return;
+ }
+
+ // Copier le token complet, pas le texte affiché
+ navigator.clipboard.writeText(fullToken);
+ showToast('success', 'Token complet copié !');
+}
+
+async function regenerateToken() {
+ const userName = document.querySelector('meta[name="user-name"]').content;
+ const userId = document.querySelector('meta[name="user-id"]').content;
+
+ const result = await Swal.fire({
+ title: 'Êtes-vous sûr ?',
+ text: "Cette action va générer un nouveau token",
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#3085d6',
+ cancelButtonColor: '#d33',
+ confirmButtonText: 'Oui, continuer',
+ cancelButtonText: 'Annuler'
+ });
+
+ if (result.isConfirmed) {
+ try {
+ const response = await fetch('/api/dpanel/generate-token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ name: userName,
+ id: userId
+ })
+ });
+
+ const data = await response.json();
+
+ if (data.token) {
+ const tokenDisplay = document.querySelector('.token-display code');
+ // D'abord stocker le token complet dans l'attribut
+ tokenDisplay.setAttribute('data-full-token', data.token);
+ // Ensuite afficher la version masquée
+ tokenDisplay.textContent = maskToken(data.token);
+
+ // Mettre à jour les boutons
+ const securityContent = document.querySelector('[data-tab-content="security"] .settings-card-content .flex');
+ securityContent.innerHTML = `
+
+
+ `;
+
+ // Copier le token complet
+ navigator.clipboard.writeText(data.token);
+
+ Swal.fire({
+ title: 'Token généré avec succès',
+ text: 'Le token complet a été copié dans votre presse-papiers.',
+ icon: 'success',
+ confirmButtonText: 'OK'
+ });
+ } else {
+ throw new Error(data.error || 'Erreur lors de la génération du token');
+ }
+ } catch (error) {
+ Swal.fire('Erreur', error.message, 'error');
+ }
+ }
+}
+
+function showToast(icon, title) {
+ Swal.fire({
+ icon,
+ title,
+ toast: true,
+ position: 'top-end',
+ showConfirmButton: false,
+ timer: 3000
+ });
+}
diff --git a/routes/Dpanel/API/Collaboration.js b/routes/Dpanel/API/Collaboration.js
new file mode 100644
index 0000000..2937aee
--- /dev/null
+++ b/routes/Dpanel/API/Collaboration.js
@@ -0,0 +1,358 @@
+const express = require('express');
+const router = express.Router();
+const path = require('path');
+const fs = require('fs').promises;
+const { logger } = require('../../../config/logs');
+
+const collaborationFilePath = path.join(__dirname, '../../../data', 'collaboration.json');
+
+// Fonction utilitaire pour lire le fichier de collaboration
+async function readCollaborationFile() {
+ try {
+ const exists = await fs.access(collaborationFilePath)
+ .then(() => true)
+ .catch(() => false);
+
+ if (!exists) {
+ await fs.writeFile(collaborationFilePath, JSON.stringify({ activeFiles: {} }));
+ return { activeFiles: {} };
+ }
+
+ const data = await fs.readFile(collaborationFilePath, 'utf8');
+ return JSON.parse(data);
+ } catch (error) {
+ logger.error('Error reading collaboration file:', error);
+ return { activeFiles: {} };
+ }
+}
+
+// Fonction utilitaire pour écrire dans le fichier de collaboration
+async function writeCollaborationFile(data) {
+ try {
+ await fs.writeFile(collaborationFilePath, JSON.stringify(data, null, 2));
+ return true;
+ } catch (error) {
+ logger.error('Error writing collaboration file:', error);
+ return false;
+ }
+}
+
+router.post('/toggle', async (req, res) => {
+ try {
+ const { itemName, itemType, enable } = req.body;
+
+ // Lire ou créer le fichier de collaboration
+ let collaborationData = await readCollaborationFile();
+
+ // S'assurer que la structure est correcte
+ if (!collaborationData.activeFiles) {
+ collaborationData.activeFiles = {};
+ }
+
+ // Mettre à jour le statut de collaboration
+ const itemId = `${itemType}-${itemName}`;
+ if (enable) {
+ collaborationData.activeFiles[itemId] = {
+ name: itemName,
+ type: itemType,
+ isCollaborative: true,
+ activeUsers: [],
+ lastModified: new Date().toISOString()
+ };
+ } else {
+ delete collaborationData.activeFiles[itemId];
+ }
+
+ // Sauvegarder les changements
+ await writeCollaborationFile(collaborationData);
+
+ // Notifier via WebSocket si disponible
+ if (req.app.get('wsManager')) {
+ req.app.get('wsManager').broadcast({
+ type: 'collaborationStatus',
+ itemName,
+ itemType,
+ isCollaborative: enable,
+ activeUsers: collaborationData.activeFiles[itemId]?.activeUsers || []
+ });
+ }
+
+ res.json({
+ success: true,
+ isCollaborative: enable,
+ activeUsers: collaborationData.activeFiles[itemId]?.activeUsers || []
+ });
+
+ } catch (error) {
+ logger.error('Error in collaboration toggle:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Internal server error'
+ });
+ }
+});
+
+// Route pour obtenir le statut d'un fichier
+router.get('/file-status/:fileId', async (req, res) => {
+ try {
+ const { fileId } = req.params;
+ const collaboration = await readCollaborationFile();
+
+ res.json({
+ fileId,
+ isCollaborative: true,
+ activeUsers: collaboration.activeFiles[fileId]?.activeUsers || [],
+ lastModified: collaboration.activeFiles[fileId]?.lastModified || null
+ });
+ } catch (error) {
+ logger.error('Error getting file status:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+router.get('/searchuser', async (req, res) => {
+ try {
+ const { username } = req.query;
+ const userFilePath = path.join(__dirname, '../../../data', 'user.json');
+ const users = JSON.parse(await fs.readFile(userFilePath, 'utf8'));
+
+ const user = users.find(u => u.name.toLowerCase() === username.toLowerCase());
+
+ if (user) {
+ res.json({
+ found: true,
+ user: {
+ id: user.id,
+ name: user.name,
+ profilePicture: user.profilePicture
+ }
+ });
+ } else {
+ res.json({ found: false });
+ }
+ } catch (error) {
+ logger.error('Error searching user:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Route pour rejoindre un fichier
+router.post('/join', async (req, res) => {
+ try {
+ const { itemName, itemType, userId } = req.body;
+ const collaboration = await readCollaborationFile();
+
+ const itemId = `${itemType}-${itemName}`;
+
+ if (!collaboration.activeFiles[itemId]) {
+ collaboration.activeFiles[itemId] = {
+ name: itemName,
+ type: itemType,
+ isCollaborative: true,
+ activeUsers: [],
+ lastModified: new Date().toISOString()
+ };
+ }
+
+ // Ajouter l'utilisateur si pas déjà présent
+ if (!collaboration.activeFiles[itemId].activeUsers.find(u => u.id === userId)) {
+ // Récupérer les infos de l'utilisateur
+ const userFilePath = path.join(__dirname, '../../../data', 'user.json');
+ const users = JSON.parse(await fs.readFile(userFilePath, 'utf8'));
+ const user = users.find(u => u.id === userId);
+
+ if (user) {
+ collaboration.activeFiles[itemId].activeUsers.push({
+ id: user.id,
+ name: user.name,
+ profilePicture: user.profilePicture,
+ lastActive: new Date().toISOString()
+ });
+
+ await writeCollaborationFile(collaboration);
+
+ // Notifier via WebSocket
+ if (req.app.get('wsManager')) {
+ req.app.get('wsManager').broadcast({
+ type: 'collaborationStatus',
+ itemName,
+ itemType,
+ isCollaborative: true,
+ activeUsers: collaboration.activeFiles[itemId].activeUsers
+ });
+ }
+ }
+ }
+
+ res.json({ success: true });
+ } catch (error) {
+ logger.error('Error in join collaboration:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Route pour ajouter un collaborateur
+router.post('/add', async (req, res) => {
+ try {
+ const { itemName, itemType, userId } = req.body;
+ const collaboration = await readCollaborationFile();
+
+ const itemId = `${itemType}-${itemName}`;
+
+ if (!collaboration.activeFiles[itemId]) {
+ return res.status(404).json({ error: 'Item not found in collaboration' });
+ }
+
+ // Vérifier si l'utilisateur n'est pas déjà dans la liste
+ if (!collaboration.activeFiles[itemId].activeUsers.find(u => u.id === userId)) {
+ // Récupérer les infos de l'utilisateur
+ const userFilePath = path.join(__dirname, '../../../data', 'user.json');
+ const users = JSON.parse(await fs.readFile(userFilePath, 'utf8'));
+ const user = users.find(u => u.id === userId);
+
+ if (user) {
+ collaboration.activeFiles[itemId].activeUsers.push({
+ id: user.id,
+ name: user.name,
+ profilePicture: user.profilePicture,
+ lastActive: new Date().toISOString()
+ });
+
+ await writeCollaborationFile(collaboration);
+
+ // Notifier via WebSocket
+ if (req.app.get('wsManager')) {
+ req.app.get('wsManager').broadcast({
+ type: 'collaborationStatus',
+ itemName,
+ itemType,
+ isCollaborative: true,
+ activeUsers: collaboration.activeFiles[itemId].activeUsers
+ });
+ }
+
+ res.json({ success: true });
+ } else {
+ res.status(404).json({ error: 'User not found' });
+ }
+ } else {
+ res.status(400).json({ error: 'User already in collaboration' });
+ }
+ } catch (error) {
+ logger.error('Error adding collaborator:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Route pour retirer un collaborateur
+router.post('/remove', async (req, res) => {
+ try {
+ const { itemName, itemType, userId } = req.body;
+ const collaboration = await readCollaborationFile();
+
+ const itemId = `${itemType}-${itemName}`;
+
+ if (!collaboration.activeFiles[itemId]) {
+ return res.status(404).json({ error: 'Item not found in collaboration' });
+ }
+
+ // Retirer l'utilisateur de la liste
+ collaboration.activeFiles[itemId].activeUsers =
+ collaboration.activeFiles[itemId].activeUsers.filter(user => user.id !== userId);
+
+ await writeCollaborationFile(collaboration);
+
+ // Notifier via WebSocket
+ if (req.app.get('wsManager')) {
+ req.app.get('wsManager').broadcast({
+ type: 'collaborationStatus',
+ itemName,
+ itemType,
+ isCollaborative: true,
+ activeUsers: collaboration.activeFiles[itemId].activeUsers
+ });
+ }
+
+ res.json({ success: true });
+ } catch (error) {
+ logger.error('Error removing collaborator:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Route pour quitter un fichier
+router.post('/leave', async (req, res) => {
+ try {
+ const { fileId } = req.body;
+ const userId = req.user.id;
+
+ if (!fileId || !userId) {
+ return res.status(400).json({ error: 'FileId and userId are required' });
+ }
+
+ const collaboration = await readCollaborationFile();
+
+ if (collaboration.activeFiles[fileId]) {
+ collaboration.activeFiles[fileId].activeUsers =
+ collaboration.activeFiles[fileId].activeUsers.filter(user => user.id !== userId);
+
+ if (collaboration.activeFiles[fileId].activeUsers.length === 0) {
+ delete collaboration.activeFiles[fileId];
+ }
+
+ await writeCollaborationFile(collaboration);
+ if (req.app.get('wsManager')) {
+ req.app.get('wsManager').broadcastFileStatus(fileId);
+ }
+ }
+
+ res.json({ success: true });
+ } catch (error) {
+ logger.error('Error leaving file:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+router.get('/status', async (req, res) => {
+ try {
+ const collaboration = await readCollaborationFile();
+ res.json({ items: collaboration.activeFiles });
+ } catch (error) {
+ logger.error('Error getting collaboration status:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Route pour obtenir les détails d'un élément collaboratif
+router.get('/details/:type/:name', async (req, res) => {
+ try {
+ const { type, name } = req.params;
+ const collaboration = await readCollaborationFile();
+ const itemId = `${type}-${name}`;
+
+ res.json(collaboration.activeFiles[itemId] || {
+ isCollaborative: false,
+ activeUsers: []
+ });
+ } catch (error) {
+ logger.error('Error getting collaboration details:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Route pour obtenir les utilisateurs actifs d'un élément
+router.get('/users/:type/:name', async (req, res) => {
+ try {
+ const { type, name } = req.params;
+ const collaboration = await readCollaborationFile();
+ const itemId = `${type}-${name}`;
+
+ const activeUsers = collaboration.activeFiles[itemId]?.activeUsers || [];
+ res.json({ users: activeUsers });
+ } catch (error) {
+ logger.error('Error getting active users:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/routes/Dpanel/API/MoveFile.js b/routes/Dpanel/API/MoveFile.js
index 434bbb6..bf962c3 100644
--- a/routes/Dpanel/API/MoveFile.js
+++ b/routes/Dpanel/API/MoveFile.js
@@ -141,13 +141,48 @@ router.get('/', (req, res) => {
});
router.post('/', authenticateToken, async (req, res) => {
+ console.log('MoveFile API - Raw request body:', req.body);
+ console.log('MoveFile API - Request body keys:', Object.keys(req.body));
+
const fileName = req.body.fileName;
const folderName = req.body.folderName;
- if (!fileName || fileName.trim() === '') {
+ console.log('MoveFile API - Received data:', {
+ fileName: fileName,
+ folderName: folderName,
+ typeOfFileName: typeof fileName,
+ typeOfFolderName: typeof folderName,
+ fileNameStringified: JSON.stringify(fileName),
+ folderNameStringified: JSON.stringify(folderName),
+ fullBody: req.body
+ });
+
+ // Forcer la conversion en string si ce sont des objets
+ let finalFileName = fileName;
+ let finalFolderName = folderName;
+
+ if (typeof fileName === 'object' && fileName !== null) {
+ console.log('fileName is an object, attempting conversion:', fileName);
+ finalFileName = String(fileName);
+ console.log('Converted fileName to:', finalFileName, typeof finalFileName);
+ }
+
+ if (typeof folderName === 'object' && folderName !== null) {
+ console.log('folderName is an object, attempting conversion:', folderName);
+ finalFolderName = String(folderName);
+ console.log('Converted folderName to:', finalFolderName, typeof finalFolderName);
+ }
+
+ if (!finalFileName || (typeof finalFileName === 'string' && finalFileName.trim() === '')) {
return res.status(400).json({ error: 'No file selected for moving.' });
}
+ // Vérifier que folderName est une chaîne de caractères
+ if (finalFolderName && typeof finalFolderName !== 'string') {
+ console.error('folderName is not a string after conversion:', finalFolderName, typeof finalFolderName);
+ return res.status(400).json({ error: 'Invalid folder name format.' });
+ }
+
try {
const data = await fs.promises.readFile(path.join(__dirname, '../../../data', 'user.json'), 'utf-8');
const users = JSON.parse(data);
@@ -160,21 +195,21 @@ router.post('/', authenticateToken, async (req, res) => {
const userId = user.name;
- if (!fileName || !userId) {
- console.error('fileName or userId is undefined');
+ if (!finalFileName || !userId) {
+ console.error('finalFileName or userId is undefined');
return res.status(500).json({ error: 'Error moving the file.' });
}
- const sourcePath = path.join('cdn-files', userId, fileName);
+ const sourcePath = path.join('cdn-files', userId, finalFileName);
let destinationDir;
- if (folderName && folderName.trim() !== '') {
- destinationDir = path.join('cdn-files', userId, folderName);
+ if (finalFolderName && finalFolderName.trim() !== '') {
+ destinationDir = path.join('cdn-files', userId, finalFolderName);
} else {
destinationDir = path.join('cdn-files', userId);
}
- const destinationPath = path.join(destinationDir, fileName);
+ const destinationPath = path.join(destinationDir, finalFileName);
if (!destinationPath.startsWith(path.join('cdn-files', userId))) {
return res.status(403).json({ error: 'Unauthorized: Cannot move file outside of user directory.' });
diff --git a/routes/Dpanel/API/RenameFolder.js b/routes/Dpanel/API/RenameFolder.js
new file mode 100644
index 0000000..a340955
--- /dev/null
+++ b/routes/Dpanel/API/RenameFolder.js
@@ -0,0 +1,205 @@
+const express = require('express');
+const fs = require('fs');
+const path = require('path');
+const router = express.Router();
+const authMiddleware = require('../../../Middlewares/authMiddleware');
+const { logger, logRequestInfo, ErrorLogger, authLogger } = require('../../../config/logs');
+const bodyParser = require('body-parser');
+
+router.use(bodyParser.json());
+
+/**
+ * @swagger
+ * /folders/rename:
+ * post:
+ * security:
+ * - bearerAuth: []
+ * tags:
+ * - Folder
+ * summary: Rename a folder
+ * description: This route allows you to rename a folder. It requires authentication.
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * oldName:
+ * type: string
+ * description: The current name of the folder
+ * newName:
+ * type: string
+ * description: The new name for the folder
+ * responses:
+ * 200:
+ * description: Success
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * message:
+ * type: string
+ * 400:
+ * description: Bad Request
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * message:
+ * type: string
+ * 401:
+ * description: Unauthorized
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * message:
+ * type: string
+ * 403:
+ * description: Forbidden
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * message:
+ * type: string
+ * 404:
+ * description: Folder not found
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * message:
+ * type: string
+ * 500:
+ * description: Internal server error
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * success:
+ * type: boolean
+ * message:
+ * type: string
+ */
+
+router.post('/', authMiddleware, async (req, res) => {
+ try {
+ const userId = req.userData.name;
+ const { oldName, newName } = req.body;
+
+ // Validation des paramètres
+ if (!oldName || !newName) {
+ return res.status(400).json({
+ success: false,
+ message: 'Les noms de dossier ancien et nouveau sont requis.'
+ });
+ }
+
+ if (typeof oldName !== 'string' || typeof newName !== 'string') {
+ return res.status(400).json({
+ success: false,
+ message: 'Les noms de dossier doivent être des chaînes de caractères.'
+ });
+ }
+
+ // Nettoyer les noms (éviter les traversées de répertoire)
+ const sanitizedOldName = path.basename(oldName.trim());
+ const sanitizedNewName = path.basename(newName.trim());
+
+ if (!sanitizedOldName || !sanitizedNewName) {
+ return res.status(400).json({
+ success: false,
+ message: 'Les noms de dossier ne peuvent pas être vides.'
+ });
+ }
+
+ // Construire les chemins
+ const userDir = path.join('cdn-files', userId);
+ const oldFolderPath = path.join(userDir, sanitizedOldName);
+ const newFolderPath = path.join(userDir, sanitizedNewName);
+
+ // Vérifier que les chemins sont dans le répertoire de l'utilisateur
+ if (!oldFolderPath.startsWith(userDir) || !newFolderPath.startsWith(userDir)) {
+ ErrorLogger.error(`Unauthorized directory access attempt by user ${userId}`);
+ return res.status(403).json({
+ success: false,
+ message: 'Accès non autorisé.'
+ });
+ }
+
+ // Vérifier que le dossier source existe
+ if (!fs.existsSync(oldFolderPath)) {
+ return res.status(404).json({
+ success: false,
+ message: 'Le dossier à renommer n\'existe pas.'
+ });
+ }
+
+ // Vérifier que c'est bien un dossier
+ const stats = await fs.promises.stat(oldFolderPath);
+ if (!stats.isDirectory()) {
+ return res.status(400).json({
+ success: false,
+ message: 'Le chemin spécifié n\'est pas un dossier.'
+ });
+ }
+
+ // Vérifier que le nouveau nom n'existe pas déjà
+ if (fs.existsSync(newFolderPath)) {
+ return res.status(400).json({
+ success: false,
+ message: 'Un dossier avec ce nom existe déjà.'
+ });
+ }
+
+ // Renommer le dossier
+ await fs.promises.rename(oldFolderPath, newFolderPath);
+
+ logger.info(`Folder renamed successfully by user ${userId}: ${sanitizedOldName} -> ${sanitizedNewName}`);
+
+ res.status(200).json({
+ success: true,
+ message: 'Dossier renommé avec succès.'
+ });
+
+ } catch (error) {
+ ErrorLogger.error('Error renaming folder:', error);
+
+ if (error.code === 'ENOENT') {
+ return res.status(404).json({
+ success: false,
+ message: 'Le dossier spécifié n\'existe pas.'
+ });
+ }
+
+ if (error.code === 'EACCES') {
+ return res.status(403).json({
+ success: false,
+ message: 'Permission refusée pour renommer ce dossier.'
+ });
+ }
+
+ return res.status(500).json({
+ success: false,
+ message: 'Erreur lors du renommage du dossier.'
+ });
+ }
+});
+
+module.exports = router;
diff --git a/routes/Dpanel/API/RevokeToken.js b/routes/Dpanel/API/RevokeToken.js
new file mode 100644
index 0000000..ab50fd0
--- /dev/null
+++ b/routes/Dpanel/API/RevokeToken.js
@@ -0,0 +1,64 @@
+const fs = require('fs');
+const path = require('path');
+const router = require('express').Router();
+
+router.post('/', (req, res) => {
+ if (!req.body.userId) {
+ return res.status(400).json({
+ error: 'Bad Request. User ID is required.'
+ });
+ }
+
+ fs.readFile(path.join(__dirname, '../../../data', 'user.json'), 'utf8', (err, data) => {
+ if (err) {
+ console.error('Error reading user.json:', err);
+ return res.status(500).json({
+ error: 'Internal server error while reading user data.'
+ });
+ }
+
+ try {
+ const users = JSON.parse(data);
+
+ // Trouver l'utilisateur par ID
+ const userIndex = users.findIndex(u => u.id === req.body.userId);
+
+ if (userIndex === -1) {
+ return res.status(404).json({
+ error: 'User not found.'
+ });
+ }
+
+ // Supprimer le token de l'utilisateur
+ if (users[userIndex].token) {
+ delete users[userIndex].token;
+ } else {
+ return res.status(404).json({
+ error: 'No API key found for this user.'
+ });
+ }
+
+ // Sauvegarder les modifications
+ fs.writeFile(path.join(__dirname, '../../../data', 'user.json'), JSON.stringify(users, null, 2), (err) => {
+ if (err) {
+ console.error('Error writing user.json:', err);
+ return res.status(500).json({
+ error: 'Internal server error while saving user data.'
+ });
+ }
+
+ res.json({
+ success: true,
+ message: 'API key successfully revoked.'
+ });
+ });
+ } catch (parseErr) {
+ console.error('Error parsing user.json:', parseErr);
+ return res.status(500).json({
+ error: 'Internal server error while parsing user data.'
+ });
+ }
+ });
+});
+
+module.exports = router;
diff --git a/routes/Dpanel/API/UserSearch.js b/routes/Dpanel/API/UserSearch.js
new file mode 100644
index 0000000..f2a5761
--- /dev/null
+++ b/routes/Dpanel/API/UserSearch.js
@@ -0,0 +1,98 @@
+const express = require('express');
+const fs = require('fs');
+const path = require('path');
+const router = express.Router();
+const authMiddleware = require('../../../Middlewares/authMiddleware');
+const { logger } = require('../../../config/logs');
+
+/**
+ * @swagger
+ * /api/dpanel/users/search:
+ * get:
+ * tags:
+ * - Users
+ * summary: Search for users by name or ID
+ * parameters:
+ * - in: query
+ * name: term
+ * required: true
+ * schema:
+ * type: string
+ * description: Search term (name or ID)
+ * - in: query
+ * name: username
+ * required: false
+ * schema:
+ * type: string
+ * description: Exact username to search for
+ * responses:
+ * 200:
+ * description: Search results
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * users:
+ * type: array
+ * items:
+ * type: object
+ * properties:
+ * id:
+ * type: string
+ * name:
+ * type: string
+ * profilePicture:
+ * type: string
+ * found:
+ * type: boolean
+ * user:
+ * type: object
+ * 500:
+ * description: Internal server error
+ */
+
+router.get('/', authMiddleware, async (req, res) => {
+ try {
+ const { term, username } = req.query;
+ const userFilePath = path.join(__dirname, '../../../data', 'user.json');
+ const users = JSON.parse(fs.readFileSync(userFilePath, 'utf8'));
+
+ if (username) {
+ // Search for exact username match (for collaboration search)
+ const user = users.find(u => u.name.toLowerCase() === username.toLowerCase());
+ if (user) {
+ res.json({
+ found: true,
+ user: {
+ id: user.id,
+ name: user.name,
+ profilePicture: user.profilePicture
+ }
+ });
+ } else {
+ res.json({ found: false });
+ }
+ } else if (term) {
+ // Search for users by term (for general user search)
+ const searchTerm = term.toLowerCase();
+ const filteredUsers = users.filter(user =>
+ user.name.toLowerCase().includes(searchTerm) ||
+ user.id.toLowerCase().includes(searchTerm)
+ ).map(user => ({
+ id: user.id,
+ name: user.name,
+ profilePicture: user.profilePicture
+ }));
+
+ res.json({ users: filteredUsers });
+ } else {
+ res.status(400).json({ error: 'Search term or username required' });
+ }
+ } catch (error) {
+ logger.error('Error searching users:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+module.exports = router;
diff --git a/routes/Dpanel/Dashboard/index.js b/routes/Dpanel/Dashboard/index.js
index 15c6af2..365f2c4 100644
--- a/routes/Dpanel/Dashboard/index.js
+++ b/routes/Dpanel/Dashboard/index.js
@@ -6,95 +6,146 @@ const fileUpload = require('express-fileupload');
const authMiddleware = require('../../../Middlewares/authMiddleware');
const { loggers } = require('winston');
const ncp = require('ncp').ncp;
-let configFile = fs.readFileSync(path.join(__dirname, '../../../data', 'setup.json'), 'utf-8')
-let config = JSON.parse(configFile)[0];
const bodyParser = require('body-parser');
const crypto = require('crypto');
const os = require('os');
const { getUserData, getSetupData } = require('../../../Middlewares/watcherMiddleware');
+let configFile = fs.readFileSync(path.join(__dirname, '../../../data', 'setup.json'), 'utf-8');
+let config = JSON.parse(configFile)[0];
let setupData = getSetupData();
let userData = getUserData();
+
+// Fonction utilitaire pour lire le fichier de collaboration
+async function getCollaborativeAccess(userId) {
+ try {
+ const collaborationFilePath = path.join(__dirname, '../../../data', 'collaboration.json');
+ if (!fs.existsSync(collaborationFilePath)) {
+ return [];
+ }
+ const data = JSON.parse(await fs.promises.readFile(collaborationFilePath, 'utf8'));
+
+ const sharedFolders = [];
+
+ for (const [itemId, item] of Object.entries(data.activeFiles)) {
+ // Ne chercher que les utilisateurs qui ne sont pas le propriétaire
+ if (item.type === 'folder' &&
+ item.isCollaborative &&
+ item.activeUsers.some(u => u.id === userId)) {
+
+ // Extraire le nom du propriétaire du dossier depuis cdn-files
+ const folderPath = item.name;
+ const ownerFolder = path.join(__dirname, '../../../cdn-files');
+
+ // Parcourir tous les dossiers utilisateurs
+ const users = await fs.promises.readdir(ownerFolder);
+ for (const user of users) {
+ const userFolderPath = path.join(ownerFolder, user, folderPath);
+ if (fs.existsSync(userFolderPath)) {
+ sharedFolders.push({
+ originalPath: `${user}/${folderPath}`,
+ displayName: `${user}/${folderPath}`,
+ owner: user,
+ folderName: folderPath,
+ activeUsers: item.activeUsers
+ });
+ break; // On a trouvé le propriétaire, on peut arrêter
+ }
+ }
+ }
+ }
+ return sharedFolders;
+ } catch (error) {
+ console.error('Error getting collaborative access:', error);
+ return [];
+ }
+}
+
router.use(bodyParser.json());
router.get('/', authMiddleware, async (req, res) => {
const folderName = req.params.folderName || '';
-
+
if (!req.userData || !req.userData.name) {
return res.render('error-recovery-file', { error: 'User data is undefined or incomplete' });
}
- const userId = req.userData.id;
+ const userId = req.userData.id;
const userName = req.userData.name;
const downloadDir = path.join('cdn-files', userName);
const domain = config.domain || 'swiftlogic-labs.com';
- if (!config.domain) {
- console.error('Domain is not defined in setup.json');
- config.domain = 'swiftlogic-labs.com';
- }
-
if (!fs.existsSync(downloadDir)) {
fs.mkdirSync(downloadDir, { recursive: true });
}
try {
- fs.accessSync(downloadDir, fs.constants.R_OK | fs.constants.W_OK);
- } catch (err) {
- console.error('No access!', err);
- return res.render('error-recovery-file', { error: 'No access to directory' });
- }
-
- let fileInfoNames = [];
- try {
- const fileInfo = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../data', 'setup.json'), 'utf-8'))
-
- if (!Array.isArray(fileInfo)) {
- console.error('fileInfo is not an array. Check the contents of file_info.json');
- } else {
- fileInfoNames = fileInfo.map(file => file.fileName);
- }
- } catch (err) {
- console.error('Error reading file_info.json:', err);
- }
-
- try {
+ // Lire les fichiers du répertoire de l'utilisateur
const files = await fs.promises.readdir(downloadDir);
-
- const folders = files.filter(file => fs.statSync(path.join(downloadDir, file)).isDirectory());
-
- const fileDetails = files.map(file => {
+
+ // Séparer les fichiers et les dossiers
+ const fileDetails = await Promise.all(files.map(async file => {
const filePath = path.join(downloadDir, file);
- const stats = fs.statSync(filePath);
- const fileExtension = path.extname(file).toLowerCase();
+ const stats = await fs.promises.stat(filePath);
+ const isDirectory = stats.isDirectory();
+ const fileExtension = isDirectory ? null : path.extname(file).toLowerCase();
const encodedFileName = encodeURIComponent(file);
- const fileLink = `https://${domain}/attachments/${userId}/${encodedFileName}`;
-
- const fileType = stats.isDirectory() ? 'folder' : 'file';
-
+ const fileLink = isDirectory ? null : `https://${domain}/attachments/${userId}/${encodedFileName}`;
+
return {
name: file,
size: stats.size,
url: fileLink,
extension: fileExtension,
- type: fileType
+ type: isDirectory ? 'folder' : 'file',
+ isPersonal: true,
+ owner: userName,
+ isCollaborative: false,
+ activeUsers: []
};
+ }));
+
+ // Séparer les dossiers personnels et les fichiers
+ const personalFolders = fileDetails.filter(item => item.type === 'folder');
+ const personalFiles = fileDetails.filter(item => item.type === 'file');
+
+ // Obtenir les dossiers partagés
+ const sharedFolders = await getCollaborativeAccess(userId);
+
+ // Formater les dossiers partagés
+ const sharedFolderDetails = sharedFolders.map(folder => ({
+ name: folder.displayName,
+ type: 'shared-folder',
+ isCollaborative: true,
+ originalPath: folder.originalPath,
+ owner: folder.owner,
+ folderName: folder.folderName,
+ activeUsers: folder.activeUsers
+ }));
+
+ // Combiner tous les éléments dans l'ordre souhaité
+ const allItems = [
+ ...personalFolders, // Dossiers personnels en premier
+ ...sharedFolderDetails, // Puis les dossiers partagés
+ ...personalFiles // Et enfin les fichiers
+ ];
+
+ const availableExtensions = Array.from(new Set(
+ allItems
+ .filter(item => item.type === 'file')
+ .map(file => file.extension)
+ ));
+
+ res.render('dashboard', {
+ files: allItems, // Liste complète pour la compatibilité
+ folders: personalFolders, // Uniquement les dossiers personnels
+ extensions: availableExtensions,
+ allFolders: personalFolders,
+ folderName: folderName,
+ fileInfoNames: [],
+ userData: userData,
+ sharedFolders: sharedFolderDetails // Les dossiers partagés séparément
});
-
- function formatFileSize(fileSizeInBytes) {
- if (fileSizeInBytes < 1024 * 1024) {
- return `${(fileSizeInBytes / 1024).toFixed(2)} Ko`;
- } else if (fileSizeInBytes < 1024 * 1024 * 1024) {
- return `${(fileSizeInBytes / (1024 * 1024)).toFixed(2)} Mo`;
- } else {
- return `${(fileSizeInBytes / (1024 * 1024 * 1024)).toFixed(2)} Go`;
- }
- }
-
-
- 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, userData: userData });
} catch (err) {
console.error('Error reading directory:', err);
return res.render('error-recovery-file', { error: err.message });
diff --git a/routes/Dpanel/Folder/index.js b/routes/Dpanel/Folder/index.js
index 62a45fd..7b5539e 100644
--- a/routes/Dpanel/Folder/index.js
+++ b/routes/Dpanel/Folder/index.js
@@ -17,6 +17,90 @@ let setupData = getSetupData();
let userData = getUserData();
router.use(bodyParser.json());
+router.get('/shared/:ownerName/:folderName', authMiddleware, async (req, res) => {
+ const { ownerName, folderName } = req.params;
+ const userId = req.userData.id;
+ const userName = req.userData.name;
+
+ try {
+ // Vérifier l'accès collaboratif
+ const collaborationFilePath = path.join(__dirname, '../../../data', 'collaboration.json');
+ const collaborationData = JSON.parse(await fs.promises.readFile(collaborationFilePath, 'utf8'));
+
+ const itemId = `folder-${folderName}`;
+ const folderInfo = collaborationData.activeFiles[itemId];
+
+ if (!folderInfo || !folderInfo.isCollaborative ||
+ !folderInfo.activeUsers.some(u => u.id === userId)) {
+ return res.status(403).render('error-recovery-file', {
+ error: 'Vous n\'avez pas accès à ce dossier.'
+ });
+ }
+
+ // Accès au dossier partagé
+ const folderPath = path.join('cdn-files', ownerName, folderName);
+ const userFolderPath = path.join('cdn-files', ownerName);
+ const domain = config.domain || 'swiftlogic-labs.com';
+
+ // Lecture des fichiers
+ const entries = await fs.promises.readdir(folderPath, { withFileTypes: true });
+ const allEntries = await fs.promises.readdir(userFolderPath, { withFileTypes: true });
+
+ const folders = entries
+ .filter(entry => entry.isDirectory())
+ .map(entry => entry.name);
+
+ const allFolders = allEntries
+ .filter(entry => entry.isDirectory())
+ .map(entry => entry.name);
+
+ // Lecture des informations de fichiers
+ const fileInfoData = await fs.promises.readFile(
+ path.join(__dirname, '../../../data', 'file_info.json'),
+ 'utf-8'
+ );
+ const fileInfo = JSON.parse(fileInfoData);
+ const fileInfoNames = fileInfo.map(file => file.fileName);
+
+ // Récupération des détails des fichiers
+ const fileDetails = await Promise.all(entries.map(async entry => {
+ const filePath = path.join(folderPath, entry.name);
+ const stats = await fs.promises.stat(filePath);
+ const encodedFileName = encodeURIComponent(entry.name);
+ const fileLink = `https://${domain}/attachments/${ownerName}/${encodedFileName}`;
+
+ return {
+ name: entry.name,
+ size: stats.size,
+ url: fileLink,
+ extension: path.extname(entry.name).toLowerCase(),
+ type: entry.isDirectory() ? 'folder' : 'file'
+ };
+ }));
+
+ const availableExtensions = Array.from(new Set(fileDetails.map(file => file.extension)));
+
+ res.render('folder', {
+ files: fileDetails,
+ folders,
+ allFolders,
+ extensions: availableExtensions,
+ currentFolder: folderName,
+ folderName,
+ fileInfoNames,
+ userName,
+ isSharedFolder: true,
+ ownerName
+ });
+
+ } catch (error) {
+ console.error('Error accessing shared folder:', error);
+ res.status(500).render('error-recovery-file', {
+ error: 'Erreur lors de l\'accès au dossier partagé'
+ });
+ }
+});
+
router.get('/:folderName', authMiddleware, async (req, res) => {
const userId = req.userData.name;
const userName = req.userData.name;
diff --git a/routes/routes.js b/routes/routes.js
index b325e1f..599271d 100644
--- a/routes/routes.js
+++ b/routes/routes.js
@@ -11,12 +11,12 @@ const DpanelFolderRoute = require('./Dpanel/Folder/index.js');
const DpanelUploadRoute = require('./Dpanel/Upload.js');
const AttachmentsRoute = require('./attachments.js');
const buildMetadataRoute = require('./BuildMetaData.js');
-const DpanelBackgroundCustomRoute = require('./Dpanel/API/BackgroundCustom.js');
const getFileDashboardRoute = require('./Dpanel/API/getFile.js');
const getFileFolderRoute = require('./Dpanel/API/getFileFolder.js');
const swagger = require('../models/swagger.js');
const NewFolderRoute = require('./Dpanel/API/NewFolder.js');
const RenameFileRoute = require('./Dpanel/API/RenameFile.js');
+const RenameFolderRoute = require('./Dpanel/API/RenameFolder.js');
const DeleteFileRoute = require('./Dpanel/API/DeleteFile.js');
const MoveFileRoute = require('./Dpanel/API/MoveFile.js');
const UploadRoute = require('./Dpanel/API/Upload.js');
@@ -28,6 +28,8 @@ const GetMetaDataFileRoute = require('./Dpanel/API/GetMetaDataFile.js');
const BackgroundCustom = require('./Dpanel/API/BackgroundCustom.js');
const ProfilUser = require('./Dpanel/Dashboard/ProfilUser.js');
const PofilPictureRoute = require('./Dpanel/API/ProfilPicture.js');
+const CollaborationRoute = require('./Dpanel/API/Collaboration.js');
+const UserSearchRoute = require('./Dpanel/API/UserSearch.js');
const loginRoute = require('./Auth/Login.js');
const logoutRoute = require('./Auth/Logout.js');
@@ -40,6 +42,7 @@ const AdminSettingSetupDpanelRoute = require('./Dpanel/Admin/SettingSetup.js');
const AdminStatsLogsDpanelRoute = require('./Dpanel/Admin/Stats-Logs.js');
const AdminPrivacySecurityDpanelRoute = require('./Dpanel/Admin/Privacy-Security.js');
const GenerateTokenRoute = require('./Dpanel/API/GenerateToken.js');
+const RevokeTokenRoute = require('./Dpanel/API/RevokeToken.js');
router.use('/', indexRoute);
router.use('/attachments', AttachmentsRoute);
@@ -58,6 +61,7 @@ router.use('/dpanel/dashboard/profil', ProfilUser);
router.use('/api/dpanel/dashboard/newfolder',discordWebhookSuspisiousAlertMiddleware, logApiRequest, NewFolderRoute);
router.use('/api/dpanel/dashboard/rename',discordWebhookSuspisiousAlertMiddleware, logApiRequest, RenameFileRoute);
+router.use('/api/dpanel/folders/rename',discordWebhookSuspisiousAlertMiddleware, logApiRequest, RenameFolderRoute);
router.use('/api/dpanel/dashboard/delete',discordWebhookSuspisiousAlertMiddleware, logApiRequest, DeleteFileRoute);
router.use('/api/dpanel/dashboard/movefile',discordWebhookSuspisiousAlertMiddleware, logApiRequest, MoveFileRoute);
router.use('/api/dpanel/upload',discordWebhookSuspisiousAlertMiddleware, logApiRequest, UploadRoute);
@@ -66,12 +70,14 @@ router.use('/api/dpanel/dashboard/admin/update-setup',discordWebhookSuspisiousAl
router.use('/api/dpanel/dashboard/deletefolder',discordWebhookSuspisiousAlertMiddleware, logApiRequest, DeleteFolderRoute);
router.use('/api/dpanel/dashboard/deletefile/', discordWebhookSuspisiousAlertMiddleware, logApiRequest,DeleteFileFolderRoute);
router.use('/api/dpanel/dashboard/getmetadatafile',discordWebhookSuspisiousAlertMiddleware, logApiRequest, GetMetaDataFileRoute);
-router.use('/api/dpanel/dashboard/backgroundcustom',discordWebhookSuspisiousAlertMiddleware, logApiRequest, DpanelBackgroundCustomRoute);
+router.use('/api/dpanel/dashboard/backgroundcustom', BackgroundCustom, logApiRequest);
router.use('/api/dpanel/generate-token',discordWebhookSuspisiousAlertMiddleware, logApiRequest, GenerateTokenRoute);
+router.use('/api/dpanel/revoke-token',discordWebhookSuspisiousAlertMiddleware, logApiRequest, RevokeTokenRoute);
router.use('/api/dpanel/dashboard/getfile', getFileDashboardRoute, logApiRequest);
router.use('/api/dpanel/dashboard/getfilefolder', getFileFolderRoute, logApiRequest);
-router.use('/api/dpanel/dashboard/backgroundcustom', BackgroundCustom, logApiRequest);
router.use('/api/dpanel/dashboard/profilpicture', PofilPictureRoute, logApiRequest);
+router.use('/api/dpanel/collaboration', discordWebhookSuspisiousAlertMiddleware, logApiRequest, CollaborationRoute);
+router.use('/api/dpanel/users/search', UserSearchRoute, logApiRequest);
router.use('/auth/login', loginRoute);
router.use('/auth/logout', logoutRoute);
diff --git a/server.js b/server.js
index 0bba274..4e12672 100644
--- a/server.js
+++ b/server.js
@@ -37,8 +37,9 @@ const loadSetup = async () => {
};
// Configuration de l'application
-const configureApp = async () => {
+ const configureApp = async () => {
const setup = await loadSetup();
+ const WebSocketManager = require('./models/websocketManager.js');
// Configuration des stratégies d'authentification
if (setup.discord) require('./models/Passport-Discord.js');
@@ -90,7 +91,7 @@ const configureApp = async () => {
app.use((err, req, res, next) => {
ErrorLogger.error('Unhandled error:', err);
- res.status(500).json(response);
+ res.status(500).json({ error: 'Internal Server Error', message: err.message });
});
};
@@ -140,6 +141,11 @@ const startServer = () => {
}
});
+ // Initialiser le WebSocket Manager ici, après la création du serveur
+ const WebSocketManager = require('./models/websocketManager.js');
+ const wsManager = new WebSocketManager(server);
+ app.set('wsManager', wsManager);
+
return server;
};
diff --git a/views/dashboard.ejs b/views/dashboard.ejs
index 78179cd..add6ec2 100644
--- a/views/dashboard.ejs
+++ b/views/dashboard.ejs
@@ -29,6 +29,33 @@
+