feat: Implémentation RCON, gestion historique et corrections Docker
- Fix protocole RCON (Int32LE, Map-based response handling) - Ajout historique des commandes RCON avec persistance - Correction chemins Docker (SERVER_DIR, RCON_HOST, volumes) - Fix récupération données joueurs (world/players) - Amélioration UX login/register - Nettoyage logs de debug
This commit is contained in:
@@ -27,10 +27,11 @@ async function getServerOps() {
|
||||
try {
|
||||
if (await fs.pathExists(opsFile)) {
|
||||
const content = await fs.readFile(opsFile, 'utf-8');
|
||||
return content
|
||||
const ops = content
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line && !line.startsWith('#'));
|
||||
.filter(line => line && line !== '' && !line.startsWith('#'));
|
||||
return ops;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur lecture ops.txt:', e);
|
||||
@@ -66,14 +67,22 @@ router.post('/register', async (req, res) => {
|
||||
return res.status(400).json({ error: 'Données manquantes' });
|
||||
}
|
||||
|
||||
console.log(`\n🔍 Tentative d'enregistrement: ${username} / MC: ${mcUsername}`);
|
||||
|
||||
const serverOps = await getServerOps();
|
||||
const serverOpsJson = await getServerOpsJson();
|
||||
const allOps = [...new Set([...serverOps, ...serverOpsJson])];
|
||||
|
||||
console.log(`📋 OPs disponibles:`, allOps);
|
||||
console.log(`🔎 Cherche: "${mcUsername}"`);
|
||||
console.log(`✓ Trouvé: ${allOps.includes(mcUsername)}`);
|
||||
|
||||
if (!allOps.includes(mcUsername)) {
|
||||
console.log(`❌ OP non trouvé pour ${mcUsername}`);
|
||||
return res.status(403).json({
|
||||
error: 'Le joueur n\'est pas OP sur le serveur',
|
||||
availableOps: allOps
|
||||
availableOps: allOps,
|
||||
checkedUsername: mcUsername
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const router = express.Router();
|
||||
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
|
||||
|
||||
function isAuthenticated(req, res, next) {
|
||||
if (req.session.user) {
|
||||
if (req.session && req.session.user) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).json({ error: 'Non authentifié' });
|
||||
@@ -15,31 +15,58 @@ function isAuthenticated(req, res, next) {
|
||||
|
||||
router.get('/', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const playersDir = path.join(SERVER_DIR, 'playerdata');
|
||||
const statsDir = path.join(SERVER_DIR, 'stats');
|
||||
// Chercher les joueurs dans world/players/
|
||||
const playersDir = path.join(SERVER_DIR, 'world', 'players');
|
||||
|
||||
if (!await fs.pathExists(playersDir)) {
|
||||
console.warn('Répertoire joueurs non trouvé:', playersDir);
|
||||
return res.json({ players: [] });
|
||||
}
|
||||
|
||||
const files = await fs.readdir(playersDir);
|
||||
const uuidRegex = /^[0-9a-f-]{36}\.dat$/i;
|
||||
const playerFiles = files.filter(f => uuidRegex.test(f));
|
||||
const playerFiles = files.filter(f => f.endsWith('.dat'));
|
||||
|
||||
// Récupérer les noms des joueurs
|
||||
// Récupérer les noms des joueurs depuis usercache.json
|
||||
const usercacheFile = path.join(SERVER_DIR, 'usercache.json');
|
||||
let usercache = {};
|
||||
|
||||
if (await fs.pathExists(usercacheFile)) {
|
||||
try {
|
||||
const cache = await fs.readJson(usercacheFile);
|
||||
usercache = cache.reduce((acc, entry) => {
|
||||
acc[entry.uuid] = entry.name;
|
||||
return acc;
|
||||
}, {});
|
||||
if (Array.isArray(cache)) {
|
||||
usercache = cache.reduce((acc, entry) => {
|
||||
acc[entry.uuid] = entry.name;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur lecture usercache:', e);
|
||||
}
|
||||
} else {
|
||||
console.warn('usercache.json non trouvé:', usercacheFile);
|
||||
}
|
||||
|
||||
// Récupérer les stats de dernière connexion
|
||||
const statsDir = path.join(SERVER_DIR, 'world', 'stats');
|
||||
let statsByUuid = {};
|
||||
|
||||
if (await fs.pathExists(statsDir)) {
|
||||
try {
|
||||
const statFiles = await fs.readdir(statsDir);
|
||||
for (const file of statFiles) {
|
||||
if (file.endsWith('.json')) {
|
||||
try {
|
||||
const uuid = file.replace('.json', '');
|
||||
const stat = await fs.readJson(path.join(statsDir, file));
|
||||
statsByUuid[uuid] = stat;
|
||||
} catch (e) {
|
||||
// Ignorer les fichiers corrompus
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Erreur lecture stats:', e);
|
||||
}
|
||||
}
|
||||
|
||||
const players = playerFiles.map(file => {
|
||||
@@ -47,7 +74,7 @@ router.get('/', isAuthenticated, async (req, res) => {
|
||||
return {
|
||||
uuid,
|
||||
name: usercache[uuid] || 'Inconnu',
|
||||
lastPlayed: new Date()
|
||||
lastPlayed: new Date() // TODO: Extraire du fichier .dat si possible
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const router = express.Router();
|
||||
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
|
||||
|
||||
function isAuthenticated(req, res, next) {
|
||||
if (req.session.user) {
|
||||
if (req.session && req.session.user) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).json({ error: 'Non authentifié' });
|
||||
@@ -19,7 +19,13 @@ async function getRconConfig() {
|
||||
try {
|
||||
const content = await fs.readFile(serverPropsFile, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
let config = { host: 'localhost', port: 25575, password: '' };
|
||||
|
||||
// Utiliser RCON_HOST depuis les variables d'environnement ou localhost
|
||||
let config = {
|
||||
host: process.env.RCON_HOST || 'localhost',
|
||||
port: process.env.RCON_PORT || 25575,
|
||||
password: ''
|
||||
};
|
||||
|
||||
for (let line of lines) {
|
||||
if (line.startsWith('rcon.port=')) {
|
||||
@@ -36,7 +42,7 @@ async function getRconConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
async function addToHistory(command, response) {
|
||||
async function addToHistory(command, response, success = true, error = null) {
|
||||
try {
|
||||
const historyFile = path.join(SERVER_DIR, '.web-admin/rcon-history.json');
|
||||
await fs.ensureDir(path.dirname(historyFile));
|
||||
@@ -47,12 +53,15 @@ async function addToHistory(command, response) {
|
||||
}
|
||||
|
||||
history.push({
|
||||
timestamp: new Date(),
|
||||
timestamp: new Date().toISOString(),
|
||||
command,
|
||||
response: response.substring(0, 500)
|
||||
response: response ? response.substring(0, 1000) : null,
|
||||
success,
|
||||
error: error ? error.substring(0, 500) : null
|
||||
});
|
||||
|
||||
await fs.writeJson(historyFile, history.slice(-100), { spaces: 2 });
|
||||
// Garder les 200 dernières commandes
|
||||
await fs.writeJson(historyFile, history.slice(-200), { spaces: 2 });
|
||||
} catch (e) {
|
||||
console.error('Erreur sauvegarde historique:', e);
|
||||
}
|
||||
@@ -78,12 +87,13 @@ router.post('/command', isAuthenticated, async (req, res) => {
|
||||
const response = await rcon.sendCommand(command);
|
||||
rcon.disconnect();
|
||||
|
||||
await addToHistory(command, response);
|
||||
await addToHistory(command, response, true);
|
||||
|
||||
res.json({ response, command });
|
||||
} catch (e) {
|
||||
console.error('Erreur RCON:', e);
|
||||
rcon.disconnect();
|
||||
await addToHistory(command, null, false, e.message);
|
||||
res.status(500).json({ error: `Erreur RCON: ${e.message}` });
|
||||
}
|
||||
|
||||
@@ -96,19 +106,49 @@ router.post('/command', isAuthenticated, async (req, res) => {
|
||||
router.get('/history', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const historyFile = path.join(SERVER_DIR, '.web-admin/rcon-history.json');
|
||||
const limit = parseInt(req.query.limit) || 100;
|
||||
const search = req.query.search || '';
|
||||
|
||||
let history = [];
|
||||
if (await fs.pathExists(historyFile)) {
|
||||
const history = await fs.readJson(historyFile);
|
||||
res.json(history);
|
||||
} else {
|
||||
res.json([]);
|
||||
history = await fs.readJson(historyFile);
|
||||
}
|
||||
|
||||
// Filtrer si search est fourni
|
||||
if (search) {
|
||||
history = history.filter(h =>
|
||||
h.command.toLowerCase().includes(search.toLowerCase()) ||
|
||||
(h.response && h.response.toLowerCase().includes(search.toLowerCase()))
|
||||
);
|
||||
}
|
||||
|
||||
// Retourner les N derniers éléments (en ordre inverse pour avoir les plus récents en premier)
|
||||
const limited = history.reverse().slice(0, limit);
|
||||
|
||||
console.log(`✓ Historique RCON récupéré: ${limited.length} entrées`);
|
||||
res.json({
|
||||
total: history.length,
|
||||
count: limited.length,
|
||||
history: limited
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Erreur historique:', e);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/history', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const historyFile = path.join(SERVER_DIR, '.web-admin/rcon-history.json');
|
||||
await fs.remove(historyFile);
|
||||
console.log('✓ Historique RCON supprimé');
|
||||
res.json({ message: 'Historique supprimé' });
|
||||
} catch (e) {
|
||||
console.error('Erreur suppression historique:', e);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/restart', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const config = await getRconConfig();
|
||||
|
||||
Reference in New Issue
Block a user