Files
WebNationsGlory_ServeurBuil…/frontend/public/js/app.js
y.campiontrebouta@innotexnas.ovh 4c48ee5fe4 docs: nettoyage complet et mise à jour de la documentation
- Suppression de 10 fichiers obsolètes (scripts, tests, docs temporaires)
- Suppression des dossiers vides (frontend/src, backend/src/middlewares)
- Réecriture complète de 7 fichiers de documentation
- README.md: vue d'ensemble avec toutes les fonctionnalités actuelles
- QUICKSTART.md: guide de démarrage rapide en 3 étapes
- CONFIGURATION.md: guide de configuration complète (Docker, RCON, sécurité)
- DEPLOYMENT.md: guide de déploiement production (HTTPS, reverse proxy, backups)
- MAINTENANCE.md: guide de maintenance avec dépannage exhaustif
- INDEX.md: index de navigation simplifié
- CHANGELOG.md: historique complet v1.0.0
- Optimisation docker-compose.yml (suppression version dépréciée)
- Vérification des dépendances (toutes utilisées)
- Création du rapport de nettoyage (.cleanup-report.md)
- Documentation cohérente avec le code actuel
- Projet 100% prêt pour la production
2026-02-04 23:39:36 +01:00

1466 lines
58 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Configuration API
const API_URL = 'http://localhost:4001/api';
// State
let currentUser = null;
let isAuthenticated = false;
// Initialisation
document.addEventListener('DOMContentLoaded', () => {
checkAuth();
});
// Vérifier l'authentification
async function checkAuth() {
try {
const response = await fetch(`${API_URL}/auth/check`, {
credentials: 'include'
});
const data = await response.json();
if (data.authenticated) {
currentUser = data.user;
isAuthenticated = true;
showDashboard();
} else {
showLoginPage();
}
} catch (e) {
console.error('Erreur vérification auth:', e);
showLoginPage();
}
}
// Afficher la page de connexion
function showLoginPage() {
document.getElementById('app').innerHTML = `
<div class="login-container">
<div class="login-box">
<h1>🎮 NationsGlory Admin</h1>
<div class="message" id="loginMessage"></div>
<div id="loginForm">
<h2 style="text-align: center; margin-bottom: 20px; color: var(--primary); font-size: 18px;">Connexion</h2>
<div class="form-group">
<label>Nom d'utilisateur</label>
<input type="text" id="username" placeholder="admin">
</div>
<div class="form-group">
<label>Mot de passe</label>
<input type="password" id="password" placeholder="">
</div>
<button class="btn-primary" onclick="handleLogin()">Se connecter</button>
<div style="text-align: center; margin-top: 15px;">
<small>Pas encore de compte? <a href="#" onclick="toggleToRegister(event)" style="color: var(--primary); text-decoration: underline; cursor: pointer;">Créer un compte</a></small>
</div>
</div>
<div id="registerForm" style="display: none;">
<h2 style="text-align: center; margin-bottom: 20px; color: var(--primary); font-size: 18px;">Créer le compte Admin</h2>
<div class="form-group">
<label>Nom d'utilisateur</label>
<input type="text" id="regUsername" placeholder="admin">
</div>
<div class="form-group">
<label>Mot de passe</label>
<input type="password" id="regPassword" placeholder="">
</div>
<div class="form-group">
<label>Pseudo Minecraft (doit être OP)</label>
<input type="text" id="mcUsername" placeholder="VotreNomMC">
</div>
<button class="btn-primary" onclick="handleRegister()">Créer le compte</button>
<div style="text-align: center; margin-top: 15px;">
<small>Vous avez déjà un compte? <a href="#" onclick="toggleToLogin(event)" style="color: var(--primary); text-decoration: underline; cursor: pointer;">Se connecter</a></small>
</div>
</div>
</div>
</div>
`;
// Par défaut, montrer le formulaire de connexion
const regForm = document.getElementById('registerForm');
const loginForm = document.getElementById('loginForm');
if (regForm && loginForm) {
regForm.style.display = 'none';
loginForm.style.display = 'block';
}
}
// Connexion
async function handleLogin() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
if (!username || !password) {
showMessage('Veuillez remplir tous les champs', 'error', 'loginMessage');
return;
}
try {
const response = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
currentUser = data.user;
isAuthenticated = true;
showDashboard();
} else {
showMessage(data.error || 'Erreur de connexion', 'error', 'loginMessage');
}
} catch (e) {
console.error('Erreur connexion:', e);
showMessage('Erreur serveur', 'error', 'loginMessage');
}
}
// Enregistrement
async function handleRegister() {
const username = document.getElementById('regUsername').value;
const password = document.getElementById('regPassword').value;
const mcUsername = document.getElementById('mcUsername').value;
if (!username || !password || !mcUsername) {
showMessage('Veuillez remplir tous les champs', 'error', 'loginMessage');
return;
}
try {
const response = await fetch(`${API_URL}/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ username, password, mcUsername })
});
const data = await response.json();
if (response.ok) {
showMessage('Compte créé! Connectez-vous maintenant.', 'success', 'loginMessage');
setTimeout(() => {
document.getElementById('registerForm').style.display = 'none';
document.getElementById('loginForm').style.display = 'block';
document.getElementById('username').value = username;
document.getElementById('password').value = '';
}, 1000);
} else {
showMessage(data.error || 'Erreur d\'enregistrement', 'error', 'loginMessage');
}
} catch (e) {
console.error('Erreur enregistrement:', e);
showMessage('Erreur serveur', 'error', 'loginMessage');
}
}
// Basculer vers le formulaire de connexion
function toggleToLogin(event) {
event.preventDefault();
const regForm = document.getElementById('registerForm');
const loginForm = document.getElementById('loginForm');
if (regForm && loginForm) {
regForm.style.display = 'none';
loginForm.style.display = 'block';
document.getElementById('loginMessage').innerHTML = '';
// Effacer les champs
document.getElementById('username').value = '';
document.getElementById('password').value = '';
}
}
// Basculer vers le formulaire d'enregistrement
function toggleToRegister(event) {
event.preventDefault();
const regForm = document.getElementById('registerForm');
const loginForm = document.getElementById('loginForm');
if (regForm && loginForm) {
loginForm.style.display = 'none';
regForm.style.display = 'block';
document.getElementById('loginMessage').innerHTML = '';
// Effacer les champs
document.getElementById('regUsername').value = '';
document.getElementById('regPassword').value = '';
document.getElementById('mcUsername').value = '';
}
}
// Afficher le dashboard
function showDashboard() {
document.getElementById('app').innerHTML = `
<div class="container">
<div class="header">
<div>
<h1>🎮 NationsGlory Admin Panel</h1>
</div>
<div class="user-info">
<p>👤 <strong>${currentUser.username}</strong> (${currentUser.mcUsername})</p>
<button class="btn-danger" onclick="handleLogout()">Déconnexion</button>
</div>
</div>
<div class="message" id="dashboardMessage"></div>
<div class="nav-tabs">
<button class="active" onclick="switchTab('dashboard')">📊 Dashboard</button>
<button onclick="switchTab('console')">⌨️ Console RCON</button>
<button onclick="switchTab('logs')">📜 Logs</button>
<button onclick="switchTab('players')">👥 Joueurs</button>
<button onclick="switchTab('whitelist')">✅ Whitelist</button>
<button onclick="switchTab('backups')">💾 Backups</button>
<button onclick="switchTab('settings')">⚙️ Paramètres</button>
</div>
<!-- Dashboard -->
<div id="dashboard" class="content-section active">
${getDashboardHTML()}
</div>
<!-- Console RCON -->
<div id="console" class="content-section">
${getConsoleHTML()}
</div>
<!-- Logs -->
<div id="logs" class="content-section">
${getLogsHTML()}
</div>
<!-- Joueurs -->
<div id="players" class="content-section">
${getPlayersHTML()}
</div>
<!-- Whitelist -->
<div id="whitelist" class="content-section">
${getWhitelistHTML()}
</div>
<!-- Backups -->
<div id="backups" class="content-section">
${getBackupsHTML()}
</div>
<!-- Paramètres -->
<div id="settings" class="content-section">
${getSettingsHTML()}
</div>
</div>
`;
// Charger les données
loadDashboardData();
}
// Changer d'onglet
function switchTab(tabName) {
// Masquer tous les onglets
document.querySelectorAll('.content-section').forEach(el => {
el.classList.remove('active');
});
// Afficher l'onglet sélectionné
document.getElementById(tabName).classList.add('active');
// Mettre à jour les boutons
document.querySelectorAll('.nav-tabs button').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
// Charger les données spécifiques
if (tabName === 'console') loadConsoleData();
if (tabName === 'logs') loadLogsData();
if (tabName === 'players') loadPlayersData();
if (tabName === 'whitelist') loadWhitelistData();
if (tabName === 'backups') loadBackupsData();
if (tabName === 'settings') loadSettingsData();
}
// ========== DASHBOARD ==========
function getDashboardHTML() {
return `
<div class="section-card">
<h2>🎮 Contrôle du Serveur</h2>
<div class="btn-group">
<button class="btn-primary" onclick="saveServer()">💾 Sauvegarder</button>
<button class="btn-warning" onclick="openChangeRconPasswordModal()">🔐 Changer RCON</button>
<button class="btn-danger" id="cancelRestartBtn" style="display: none;" onclick="cancelRestart()">❌ Annuler Redémarrage</button>
<button class="btn-danger" id="cancelShutdownBtnDash" style="display: none;" onclick="cancelShutdown()">❌ Annuler Arrêt</button>
</div>
</div>
<div class="grid">
<div class="grid-card">
<h3>État Serveur</h3>
<p class="value"><span class="status online"></span>En ligne</p>
</div>
<div class="grid-card">
<h3>Joueurs Connectés</h3>
<p class="value" id="playerCount">0/20</p>
</div>
<div class="grid-card">
<h3>Version Forge</h3>
<p class="value" id="forgeVersion">1.6.4</p>
</div>
</div>
<div class="section-card">
<h2>📊 Informations Rapides</h2>
<table>
<tbody id="quickInfoTable">
<tr><td>Chargement...</td><td></td></tr>
</tbody>
</table>
</div>
`;
}
async function loadDashboardData() {
try {
// Charger les joueurs en ligne
await window.loadOnlinePlayers();
const response = await fetch(`${API_URL}/server`, {
credentials: 'include'
});
const data = await response.json();
if (data.properties) {
const table = document.getElementById('quickInfoTable');
table.innerHTML = `
<tr><td><strong>Nom Serveur</strong></td><td>${data.properties['motd'] || 'NationsGlory'}</td></tr>
<tr><td><strong>Mode Jeu</strong></td><td>${data.properties['gamemode'] || 'Survival'}</td></tr>
<tr><td><strong>Difficulté</strong></td><td>${data.properties['difficulty'] || '2'}</td></tr>
<tr><td><strong>PvP</strong></td><td>${data.properties['pvp'] === 'true' ? 'Activé' : 'Désactivé'}</td></tr>
<tr><td><strong>Port</strong></td><td>${data.properties['server-port'] || '25565'}</td></tr>
<tr><td><strong>Max Joueurs</strong></td><td>${data.properties['max-players'] || '20'}</td></tr>
`;
}
// Mettre à jour le nombre de joueurs
const onlineResponse = await fetch(`${API_URL}/rcon/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ command: 'list' })
});
if (onlineResponse.ok) {
const rconData = await onlineResponse.json();
const output = rconData.response || '';
const cleanOutput = output.replace(/§[0-9a-fk-or]/gi, '');
const match = cleanOutput.match(/There are (\d+) out of maximum (\d+) players online/i);
if (match) {
const onlineCount = parseInt(match[1]);
const maxPlayers = parseInt(match[2]);
document.getElementById('playerCount').textContent = `${onlineCount}/${maxPlayers}`;
}
}
} catch (e) {
console.error('Erreur chargement dashboard:', e);
}
}
// ========== CONSOLE RCON ==========
function getConsoleHTML() {
return `
<div class="section-card">
<h2>⌨️ Console RCON</h2>
<p style="margin-bottom: 15px; color: #666;">Exécutez des commandes directement sur le serveur</p>
<div style="background: #1e1e1e; border-radius: 8px; padding: 15px; margin-bottom: 15px; font-family: 'Courier New', monospace; border: 1px solid #333;">
<div id="consoleOutput" style="color: #0f0; min-height: 300px; max-height: 400px; overflow-y: auto; margin-bottom: 15px; line-height: 1.5; font-size: 13px; white-space: pre-wrap; word-wrap: break-word;"></div>
<div style="display: flex; gap: 5px;">
<span style="color: #0f0;">admin@nationsglory:~$</span>
<input type="text" id="commandInput" placeholder="" style="background: #1e1e1e; color: #0f0; border: none; outline: none; flex: 1; font-family: 'Courier New', monospace; font-size: 13px;" onkeypress="if(event.key==='Enter') sendRconCommand();">
</div>
</div>
<h3 style="margin-top: 30px; margin-bottom: 15px;"><3E> Actions Rapides</h3>
<div class="btn-group" style="margin-bottom: 15px; flex-wrap: wrap; gap: 10px;">
<button class="btn-primary" onclick="openSayModal()">💬 Message Chat</button>
<button class="btn-success" onclick="openRestartModal()">🔄 Redémarrer</button>
<button class="btn-warning" onclick="openShutdownModal()" style="background: #ff9800;">⏹️ Arrêter Serveur</button>
<button class="btn-danger" onclick="cancelRestart()" id="cancelRestartBtn" style="display: none;">❌ Annuler Redémarrage</button>
<button class="btn-danger" onclick="cancelShutdown()" id="cancelShutdownBtn" style="display: none;">❌ Annuler Arrêt</button>
</div>
<h3 style="margin-top: 30px; margin-bottom: 15px;">📜 Historique des Commandes</h3>
<div class="btn-group" style="margin-bottom: 15px;">
<input type="text" id="historySearch" placeholder="Chercher dans l'historique..." style="padding: 8px 12px; border-radius: 4px; border: 1px solid #ddd; flex: 1;" onchange="loadConsoleData()">
<button class="btn-secondary" onclick="loadConsoleData()">🔄 Rafraîchir</button>
<button class="btn-danger" onclick="clearRconHistory()">🗑️ Vider</button>
</div>
<div id="historyContainer" style="max-height: 400px; overflow-y: auto; background: #f8f9fa; border-radius: 5px; padding: 15px; border: 1px solid #ddd;"></div>
</div>
<!-- Modal Message Chat -->
<div id="sayModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;">
<div style="background: white; padding: 30px; border-radius: 8px; width: 90%; max-width: 500px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<h3 style="margin-top: 0; margin-bottom: 20px;">💬 Envoyer un Message</h3>
<textarea id="sayInput" placeholder="Entrez votre message..." style="width: 100%; height: 100px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-family: Arial, sans-serif; resize: vertical; box-sizing: border-box;"></textarea>
<div style="margin-top: 15px; display: flex; gap: 10px; justify-content: flex-end;">
<button class="btn-secondary" onclick="document.getElementById('sayModal').style.display='none'">Annuler</button>
<button class="btn-primary" onclick="sendSayCommand()">Envoyer</button>
</div>
</div>
</div>
<!-- Modal Arrêt Serveur -->
<div id="shutdownModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;">
<div style="background: white; padding: 30px; border-radius: 8px; width: 90%; max-width: 500px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<h3 style="margin-top: 0; margin-bottom: 20px;">⏹️ Arrêter le Serveur</h3>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 10px; font-weight: bold;">Cooldown avant arrêt (secondes):</label>
<input type="number" id="shutdownCooldown" value="60" min="10" max="600" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 10px; font-weight: bold;">Message d'avertissement:</label>
<textarea id="shutdownMessage" placeholder="Message à envoyer aux joueurs..." style="width: 100%; height: 80px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-family: Arial, sans-serif; resize: vertical; box-sizing: border-box;">Le serveur redémarre dans {cooldown} secondes!</textarea>
</div>
<p style="color: #ff9800; background: #fff3e0; padding: 10px; border-radius: 4px; margin-bottom: 15px; font-size: 13px;">⚠️ Un message d'avertissement sera envoyé tous les 10 secondes</p>
<div style="display: flex; gap: 10px; justify-content: flex-end;">
<button class="btn-secondary" onclick="document.getElementById('shutdownModal').style.display='none'">Annuler</button>
<button class="btn-danger" style="background: #dc3545;" onclick="shutdownServer()">Confirmer l'Arrêt</button>
</div>
</div>
</div>
<!-- Modal Redémarrage Serveur -->
<div id="restartModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;">
<div style="background: white; padding: 30px; border-radius: 8px; width: 90%; max-width: 500px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<h3 style="margin-top: 0; margin-bottom: 20px;">🔄 Redémarrer le Serveur</h3>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 10px; font-weight: bold;">Cooldown avant redémarrage (secondes):</label>
<input type="number" id="restartCooldown" value="60" min="10" max="600" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 10px; font-weight: bold;">Message d'avertissement:</label>
<textarea id="restartMessage" placeholder="Message à envoyer aux joueurs..." style="width: 100%; height: 80px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-family: Arial, sans-serif; resize: vertical; box-sizing: border-box;">Le serveur redémarre dans {cooldown} secondes!</textarea>
</div>
<p style="color: #2196F3; background: #e3f2fd; padding: 10px; border-radius: 4px; margin-bottom: 15px; font-size: 13px;"> Un message d'avertissement sera envoyé tous les 10 secondes</p>
<div style="display: flex; gap: 10px; justify-content: flex-end;">
<button class="btn-secondary" onclick="document.getElementById('restartModal').style.display='none'">Annuler</button>
<button class="btn-success" onclick="restartServer()">Confirmer le Redémarrage</button>
</div>
</div>
</div>
`;
}
async function loadConsoleData() {
try {
const search = document.getElementById('historySearch')?.value || '';
const url = new URL(`${API_URL}/rcon/history`);
url.searchParams.set('limit', '50');
if (search) url.searchParams.set('search', search);
const response = await fetch(url, {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
const container = document.getElementById('historyContainer');
if (data.history && data.history.length > 0) {
container.innerHTML = data.history.map(h => {
const timestamp = new Date(h.timestamp).toLocaleString('fr-FR');
const statusIcon = h.success ? '✓' : '✗';
const statusColor = h.success ? '#28a745' : '#dc3545';
return `
<div style="margin-bottom: 12px; padding: 10px; background: white; border-left: 3px solid ${statusColor}; border-radius: 3px; font-size: 13px;">
<div style="display: flex; justify-content: space-between; align-items: start;">
<div style="flex: 1;">
<strong style="color: var(--primary); word-break: break-all;">${h.command}</strong><br>
<span style="color: #666; font-size: 11px;">${timestamp}</span>
</div>
<span style="color: ${statusColor}; font-weight: bold; margin-left: 10px;">${statusIcon}</span>
</div>
${h.response ? `<div style="color: #333; margin-top: 5px; padding-top: 5px; border-top: 1px solid #eee; font-size: 12px; max-height: 80px; overflow-y: auto; word-break: break-word;">${h.response}</div>` : ''}
${h.error ? `<div style="color: #dc3545; margin-top: 5px; padding-top: 5px; border-top: 1px solid #eee; font-size: 12px;">Erreur: ${h.error}</div>` : ''}
</div>
`;
}).join('');
} else {
container.innerHTML = '<p style="color: #666; text-align: center; padding: 20px;">Aucun historique</p>';
}
} catch (e) {
console.error('Erreur historique:', e);
const container = document.getElementById('historyContainer');
container.innerHTML = `<p style="color: #dc3545;">Erreur lors du chargement: ${e.message}</p>`;
}
}
async function clearRconHistory() {
if (!confirm('Êtes-vous sûr de vouloir supprimer tout l\'historique?')) return;
try {
const response = await fetch(`${API_URL}/rcon/history`, {
method: 'DELETE',
credentials: 'include'
});
if (response.ok) {
showMessage('Historique supprimé', 'success', 'dashboardMessage');
loadConsoleData();
} else {
showMessage('Erreur lors de la suppression', 'error', 'dashboardMessage');
}
} catch (e) {
console.error('Erreur suppression historique:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
}
}
async function sendRconCommand() {
const commandInput = document.getElementById('commandInput');
const command = commandInput.value.trim();
if (!command) return;
const output = document.getElementById('consoleOutput');
output.textContent += `admin@nationsglory:~$ ${command}\n`;
commandInput.value = '';
try {
const response = await fetch(`${API_URL}/rcon/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ command })
});
const data = await response.json();
if (response.ok) {
const responseText = data.response || '(pas de réponse)';
output.textContent += `${responseText}\n`;
showMessage('Commande exécutée ✓', 'success', 'dashboardMessage');
loadConsoleData(); // Rafraîchir l'historique
} else {
output.textContent += `Erreur: ${data.error}\n`;
showMessage(data.error, 'error', 'dashboardMessage');
}
output.scrollTop = output.scrollHeight;
} catch (e) {
console.error('Erreur envoi commande:', e);
output.textContent += `Erreur serveur: ${e.message}\n`;
showMessage('Erreur serveur', 'error', 'dashboardMessage');
output.scrollTop = output.scrollHeight;
}
}
// Variables globales pour gérer l'arrêt du serveur
let shutdownInProgress = false;
let shutdownCancelToken = null;
window.openSayModal = function() {
document.getElementById('sayModal').style.display = 'flex';
};
window.sendSayCommand = async function() {
const message = document.getElementById('sayInput').value.trim();
if (!message) {
alert('Entrez un message');
return;
}
try {
const response = await fetch(`${API_URL}/rcon/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ command: `say ${message}` })
});
if (response.ok) {
showMessage('Message envoyé ✓', 'success', 'dashboardMessage');
document.getElementById('sayInput').value = '';
document.getElementById('sayModal').style.display = 'none';
loadConsoleData();
} else {
showMessage('Erreur lors de l\'envoi du message', 'error', 'dashboardMessage');
}
} catch (e) {
console.error('Erreur envoi message:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
}
};
window.openShutdownModal = function() {
document.getElementById('shutdownModal').style.display = 'flex';
};
window.shutdownServer = async function() {
const cooldown = parseInt(document.getElementById('shutdownCooldown').value);
const message = document.getElementById('shutdownMessage').value.trim();
if (cooldown < 10 || cooldown > 600) {
alert('Le cooldown doit être entre 10 et 600 secondes');
return;
}
if (!message) {
alert('Entrez un message d\'avertissement');
return;
}
if (!confirm(`Êtes-vous sûr de vouloir arrêter le serveur dans ${cooldown} secondes?`)) {
return;
}
try {
shutdownInProgress = true;
shutdownCancelToken = {};
document.getElementById('cancelShutdownBtn').style.display = 'block';
document.getElementById('shutdownModal').style.display = 'none';
showMessage(`⏳ Arrêt du serveur dans ${cooldown} secondes...`, 'warning', 'dashboardMessage');
let secondsRemaining = cooldown;
while (secondsRemaining > 0 && shutdownInProgress && shutdownCancelToken) {
if (secondsRemaining % 10 === 0 || secondsRemaining <= 10) {
// Envoyer un avertissement tous les 10 secondes (ou chaque seconde dans les 10 dernières)
const warningMsg = message.replace('{cooldown}', secondsRemaining);
await fetch(`${API_URL}/rcon/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ command: `say ${warningMsg}` })
}).catch(() => {});
}
await new Promise(resolve => setTimeout(resolve, 1000));
secondsRemaining--;
}
if (!shutdownInProgress || !shutdownCancelToken) {
// Envoyer un message dans le chat pour informer de l'annulation
await fetch(`${API_URL}/rcon/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ command: 'say Arrêt du serveur annulé!' })
}).catch(() => {});
showMessage('❌ Arrêt annulé', 'warning', 'dashboardMessage');
document.getElementById('cancelShutdownBtn').style.display = 'none';
document.getElementById('cancelShutdownBtnDash').style.display = 'none';
return;
}
// Envoyer la commande d'arrêt
const response = await fetch(`${API_URL}/rcon/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ command: 'stop' })
});
if (response.ok) {
showMessage('✓ Serveur arrêté', 'success', 'dashboardMessage');
} else {
showMessage('Erreur lors de l\'arrêt du serveur', 'error', 'dashboardMessage');
}
shutdownInProgress = false;
shutdownCancelToken = null;
document.getElementById('cancelShutdownBtn').style.display = 'none';
document.getElementById('cancelShutdownBtnDash').style.display = 'none';
loadConsoleData();
} catch (e) {
console.error('Erreur arrêt serveur:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
shutdownInProgress = false;
shutdownCancelToken = null;
document.getElementById('cancelShutdownBtn').style.display = 'none';
}
};
window.cancelShutdown = function() {
if (shutdownInProgress) {
shutdownInProgress = false;
shutdownCancelToken = null;
document.getElementById('cancelShutdownBtn').style.display = 'none';
document.getElementById('cancelShutdownBtnDash').style.display = 'none';
showMessage('❌ Arrêt annulé', 'warning', 'dashboardMessage');
}
};
// Variables globales pour gérer le redémarrage du serveur
let restartInProgress = false;
let restartCancelToken = null;
window.openRestartModal = function() {
document.getElementById('restartModal').style.display = 'flex';
};
window.restartServer = async function() {
const cooldown = parseInt(document.getElementById('restartCooldown').value);
const message = document.getElementById('restartMessage').value.trim();
if (cooldown < 10 || cooldown > 600) {
alert('Le cooldown doit être entre 10 et 600 secondes');
return;
}
if (!message) {
alert('Entrez un message d\'avertissement');
return;
}
if (!confirm(`Êtes-vous sûr de vouloir redémarrer le serveur dans ${cooldown} secondes?`)) {
return;
}
try {
restartInProgress = true;
restartCancelToken = {};
document.getElementById('cancelRestartBtn').style.display = 'block';
document.getElementById('restartModal').style.display = 'none';
showMessage(`⏳ Redémarrage du serveur dans ${cooldown} secondes...`, 'warning', 'dashboardMessage');
let secondsRemaining = cooldown;
while (secondsRemaining > 0 && restartInProgress && restartCancelToken) {
if (secondsRemaining % 10 === 0 || secondsRemaining <= 10) {
// Envoyer un avertissement tous les 10 secondes (ou chaque seconde dans les 10 dernières)
const warningMsg = message.replace('{cooldown}', secondsRemaining);
await fetch(`${API_URL}/rcon/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ command: `say ${warningMsg}` })
}).catch(() => {});
}
await new Promise(resolve => setTimeout(resolve, 1000));
secondsRemaining--;
}
if (!restartInProgress || !restartCancelToken) {
// Envoyer un message dans le chat pour informer de l'annulation
await fetch(`${API_URL}/rcon/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ command: 'say Redémarrage du serveur annulé!' })
}).catch(() => {});
showMessage('❌ Redémarrage annulé', 'warning', 'dashboardMessage');
document.getElementById('cancelRestartBtn').style.display = 'none';
return;
}
// Envoyer la commande de redémarrage
const response = await fetch(`${API_URL}/rcon/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ command: 'stop' })
});
if (response.ok) {
showMessage('✓ Serveur redémarré', 'success', 'dashboardMessage');
} else {
showMessage('Erreur lors du redémarrage du serveur', 'error', 'dashboardMessage');
}
restartInProgress = false;
restartCancelToken = null;
document.getElementById('cancelRestartBtn').style.display = 'none';
loadDashboardData();
} catch (e) {
console.error('Erreur redémarrage serveur:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
restartInProgress = false;
restartCancelToken = null;
document.getElementById('cancelRestartBtn').style.display = 'none';
}
};
window.cancelRestart = function() {
if (restartInProgress) {
restartInProgress = false;
restartCancelToken = null;
document.getElementById('cancelRestartBtn').style.display = 'none';
showMessage('❌ Redémarrage annulé', 'warning', 'dashboardMessage');
}
};
// ========== LOGS ==========
function getLogsHTML() {
return `
<div class="section-card">
<h2>📜 Logs</h2>
<div class="btn-group">
<button class="btn-primary" onclick="reloadLogs()">🔄 Rafraîchir</button>
<button class="btn-secondary" onclick="openSearchLogsModal()">🔍 Chercher</button>
</div>
<div class="logs-container" id="logsContainer"></div>
</div>
`;
}
async function loadLogsData() {
try {
const response = await fetch(`${API_URL}/logs?lines=200`, {
credentials: 'include'
});
const data = await response.json();
const container = document.getElementById('logsContainer');
container.innerHTML = data.logs.map((line, idx) => {
let className = '';
if (line.includes('ERROR')) className = 'error';
else if (line.includes('WARN')) className = 'warning';
return `<div class="log-line ${className}">${escapeHtml(line)}</div>`;
}).join('');
container.scrollTop = container.scrollHeight;
} catch (e) {
console.error('Erreur chargement logs:', e);
document.getElementById('logsContainer').innerHTML = '<div class="error">Erreur chargement</div>';
}
}
async function reloadLogs() {
showMessage('Rechargement...', 'success', 'dashboardMessage');
await loadLogsData();
showMessage('Logs rechargés', 'success', 'dashboardMessage');
}
function openSearchLogsModal() {
const query = prompt('Chercher dans les logs:');
if (query) searchLogs(query);
}
async function searchLogs(query) {
try {
const response = await fetch(`${API_URL}/logs/search?query=${encodeURIComponent(query)}`, {
credentials: 'include'
});
const data = await response.json();
const container = document.getElementById('logsContainer');
container.innerHTML = data.results.map((r, idx) => `
<div class="log-line" style="background: ${idx % 2 === 0 ? '#f8f9fa' : 'white'}; padding: 5px;">
<strong style="color: var(--accent);">[${r.index}]</strong> ${escapeHtml(r.line)}
</div>
`).join('');
} catch (e) {
console.error('Erreur recherche:', e);
}
}
// ========== JOUEURS ==========
function getPlayersHTML() {
return `
<div class="section-card">
<h2>👥 Joueurs en Ligne</h2>
<div class="btn-group" style="margin-bottom: 15px;">
<button class="btn-secondary" onclick="loadOnlinePlayers()">🔄 Rafraîchir</button>
</div>
<div id="onlinePlayersInfo" style="padding: 15px; background: #f5f5f5; border-radius: 8px; margin-bottom: 20px;">
<p style="margin: 0; font-size: 16px; font-weight: bold;">Chargement...</p>
</div>
<div id="onlinePlayersList"></div>
</div>
<div class="section-card">
<h2>📋 Tous les Joueurs</h2>
<p style="margin-bottom: 15px; color: #666;">Liste des joueurs qui se sont connectés au serveur</p>
<table>
<thead>
<tr>
<th>Nom</th>
<th>UUID</th>
<th>Dernière Connexion</th>
</tr>
</thead>
<tbody id="playersTable">
<tr><td colspan="3" style="text-align: center;">Chargement...</td></tr>
</tbody>
</table>
</div>
`;
}
async function loadPlayersData() {
// Charger les joueurs en ligne
await window.loadOnlinePlayers();
// Charger tous les joueurs
try {
const response = await fetch(`${API_URL}/players`, {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
const table = document.getElementById('playersTable');
if (data.players && data.players.length > 0) {
table.innerHTML = data.players.map(p => `
<tr>
<td>${p.name}</td>
<td><code style="font-size: 11px;">${p.uuid}</code></td>
<td>${new Date(p.lastPlayed).toLocaleString()}</td>
</tr>
`).join('');
} else {
table.innerHTML = '<tr><td colspan="3" style="text-align: center;">Aucun joueur</td></tr>';
}
} catch (e) {
console.error('Erreur joueurs:', e);
document.getElementById('playersTable').innerHTML = `<tr><td colspan="3" style="text-align: center; color: red;">Erreur: ${e.message}</td></tr>`;
}
}
window.loadOnlinePlayers = async function() {
console.log('Chargement des joueurs en ligne...');
const infoDiv = document.getElementById('onlinePlayersInfo');
const listDiv = document.getElementById('onlinePlayersList');
if (!infoDiv || !listDiv) {
console.error('Éléments DOM non trouvés');
return;
}
try {
infoDiv.innerHTML = '<p style="margin: 0;">⏳ Chargement...</p>';
const response = await fetch(`${API_URL}/rcon/command`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({ command: 'list' })
});
console.log('Réponse RCON reçue:', response.status);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
const output = data.response || data.output || '';
console.log('Données reçues:', data);
console.log('Output RCON:', output);
// Supprimer les codes de couleur Minecraft (§X)
const cleanOutput = output.replace(/§[0-9a-fk-or]/gi, '');
console.log('Output nettoyé:', cleanOutput);
// Parser différents formats possibles:
// Format moderne: "There are 1/20 players online: player1, player2"
// Format 1.6.4: "There are 1 out of maximum 20 players online. Connected players: player1"
let match = cleanOutput.match(/There are (\d+)\/(\d+) players online:?\s*(.*)/i);
if (!match) {
// Essayer le format 1.6.4
match = cleanOutput.match(/There are (\d+) out of maximum (\d+) players online/i);
if (match) {
const onlineCount = parseInt(match[1]);
const maxPlayers = parseInt(match[2]);
// Chercher les noms de joueurs après "Connected players:"
const playersMatch = cleanOutput.match(/Connected players[:\s]+(.+)/i);
const playerNames = playersMatch ?
playersMatch[1].split(',').map(n => n.trim().replace(/\[AFK\]/gi, '').trim()).filter(n => n) :
[];
match = [cleanOutput, onlineCount.toString(), maxPlayers.toString(), playerNames.join(', ')];
}
}
if (match) {
const onlineCount = parseInt(match[1]);
const maxPlayers = parseInt(match[2]);
const playerNames = match[3] ? match[3].split(',').map(n => n.trim()).filter(n => n) : [];
console.log(`Joueurs: ${onlineCount}/${maxPlayers}`, playerNames);
infoDiv.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center;">
<p style="margin: 0; font-size: 16px;">
<span style="font-weight: bold; color: ${onlineCount > 0 ? '#4CAF50' : '#666'};">
${onlineCount} joueur${onlineCount !== 1 ? 's' : ''}
</span>
en ligne sur ${maxPlayers}
</p>
<span style="font-size: 24px;">${onlineCount > 0 ? '🟢' : '⚪'}</span>
</div>
`;
if (onlineCount > 0 && playerNames.length > 0) {
listDiv.innerHTML = `
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; margin-top: 15px;">
${playerNames.map(name => `
<div style="padding: 12px; background: white; border: 2px solid #4CAF50; border-radius: 8px; text-align: center;">
<span style="font-weight: bold; color: #333;">🎮 ${name}</span>
</div>
`).join('')}
</div>
`;
} else {
listDiv.innerHTML = '';
}
} else {
infoDiv.innerHTML = `<p style="margin: 0; color: #666;">Aucun joueur en ligne</p>`;
listDiv.innerHTML = '';
}
} catch (e) {
console.error('Erreur chargement joueurs en ligne:', e);
infoDiv.innerHTML = `
<p style="margin: 0; color: red;">Erreur: ${e.message}</p>
`;
listDiv.innerHTML = '';
}
}
// ========== WHITELIST ==========
function getWhitelistHTML() {
return `
<div class="section-card">
<h2>✅ Whitelist</h2>
<div class="btn-group">
<button class="btn-primary" onclick="openAddWhitelistModal()"> Ajouter</button>
<button class="btn-secondary" onclick="reloadWhitelist()">🔄 Rafraîchir</button>
</div>
<table>
<thead>
<tr>
<th>Joueur</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="whitelistTable">
<tr><td colspan="2" style="text-align: center;">Chargement...</td></tr>
</tbody>
</table>
</div>
`;
}
async function loadWhitelistData() {
try {
const response = await fetch(`${API_URL}/whitelist`, {
credentials: 'include'
});
const data = await response.json();
const table = document.getElementById('whitelistTable');
if (data.whitelist && data.whitelist.length > 0) {
const players = data.whitelist.map(w => typeof w === 'string' ? w : w.name);
table.innerHTML = players.map(p => `
<tr>
<td>${p}</td>
<td>
<button class="btn-danger" style="padding: 5px 10px; font-size: 12px;" onclick="removeFromWhitelist('${p}')">❌ Retirer</button>
</td>
</tr>
`).join('');
} else {
table.innerHTML = '<tr><td colspan="2" style="text-align: center;">Whitelist vide</td></tr>';
}
} catch (e) {
console.error('Erreur whitelist:', e);
document.getElementById('whitelistTable').innerHTML = '<tr><td colspan="2" style="text-align: center; color: red;">Erreur</td></tr>';
}
}
function openAddWhitelistModal() {
const username = prompt('Nom du joueur à ajouter:');
if (username) addToWhitelist(username);
}
async function addToWhitelist(username) {
try {
const response = await fetch(`${API_URL}/whitelist/add`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ username })
});
const data = await response.json();
if (response.ok) {
showMessage(`${username} ajouté à la whitelist`, 'success', 'dashboardMessage');
loadWhitelistData();
} else {
showMessage(data.error, 'error', 'dashboardMessage');
}
} catch (e) {
console.error('Erreur:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
}
}
async function removeFromWhitelist(username) {
if (!confirm(`Confirmer la suppression de ${username}?`)) return;
try {
const response = await fetch(`${API_URL}/whitelist/remove`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ username })
});
const data = await response.json();
if (response.ok) {
showMessage(`${username} retiré de la whitelist`, 'success', 'dashboardMessage');
loadWhitelistData();
} else {
showMessage(data.error, 'error', 'dashboardMessage');
}
} catch (e) {
console.error('Erreur:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
}
}
async function reloadWhitelist() {
showMessage('Rechargement...', 'success', 'dashboardMessage');
await loadWhitelistData();
showMessage('Whitelist rechargée', 'success', 'dashboardMessage');
}
// ========== BACKUPS ==========
function getBackupsHTML() {
return `
<div class="section-card">
<h2>💾 Backups</h2>
<div class="btn-group">
<button class="btn-success" onclick="createBackup()"> Créer Backup</button>
<button class="btn-secondary" onclick="reloadBackups()">🔄 Rafraîchir</button>
</div>
<table>
<thead>
<tr>
<th>Nom</th>
<th>Taille</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="backupsTable">
<tr><td colspan="4" style="text-align: center;">Chargement...</td></tr>
</tbody>
</table>
</div>
`;
}
async function loadBackupsData() {
try {
const response = await fetch(`${API_URL}/backup`, {
credentials: 'include'
});
const data = await response.json();
const table = document.getElementById('backupsTable');
if (Array.isArray(data) && data.length > 0) {
table.innerHTML = data.map(b => `
<tr>
<td>${b.name}</td>
<td>${b.size}</td>
<td>${new Date(b.created).toLocaleString()}</td>
<td>
<button class="btn-danger" style="padding: 5px 10px; font-size: 12px;" onclick="deleteBackup('${b.name}')">❌ Supprimer</button>
</td>
</tr>
`).join('');
} else {
table.innerHTML = '<tr><td colspan="4" style="text-align: center;">Aucun backup</td></tr>';
}
} catch (e) {
console.error('Erreur backups:', e);
document.getElementById('backupsTable').innerHTML = '<tr><td colspan="4" style="text-align: center; color: red;">Erreur</td></tr>';
}
}
async function createBackup() {
if (!confirm('Créer un nouveau backup? (cela peut prendre du temps)')) return;
showMessage('Création du backup en cours...', 'success', 'dashboardMessage');
try {
const response = await fetch(`${API_URL}/backup/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({})
});
const data = await response.json();
if (response.ok) {
showMessage(data.message, 'success', 'dashboardMessage');
setTimeout(() => loadBackupsData(), 2000);
} else {
showMessage(data.error, 'error', 'dashboardMessage');
}
} catch (e) {
console.error('Erreur:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
}
}
async function deleteBackup(filename) {
if (!confirm(`Confirmer la suppression du backup ${filename}?`)) return;
try {
const response = await fetch(`${API_URL}/backup/delete/${filename}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({})
});
const data = await response.json();
if (response.ok) {
showMessage('Backup supprimé', 'success', 'dashboardMessage');
loadBackupsData();
} else {
showMessage(data.error, 'error', 'dashboardMessage');
}
} catch (e) {
console.error('Erreur:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
}
}
async function reloadBackups() {
showMessage('Rechargement...', 'success', 'dashboardMessage');
await loadBackupsData();
showMessage('Backups rechargés', 'success', 'dashboardMessage');
}
// ========== PARAMÈTRES ==========
function getSettingsHTML() {
return `
<div class="section-card">
<h2>⚙️ Paramètres du Serveur</h2>
<p style="margin-bottom: 15px; color: #666;">Modifiez les paramètres du serveur en temps réel</p>
<div style="margin-bottom: 15px;">
<button class="btn-secondary" onclick="loadSettingsData()">🔄 Rafraîchir</button>
</div>
<table id="settingsTable" style="width: 100%;">
<thead>
<tr>
<th style="text-align: left; width: 30%; padding: 10px; border-bottom: 2px solid #ddd;">Paramètre</th>
<th style="text-align: left; width: 70%; padding: 10px; border-bottom: 2px solid #ddd;">Valeur</th>
</tr>
</thead>
<tbody>
<tr><td colspan="2" style="text-align: center; padding: 20px;">Chargement...</td></tr>
</tbody>
</table>
</div>
`;
}
async function loadSettingsData() {
try {
const response = await fetch(`${API_URL}/server`, {
credentials: 'include'
});
const data = await response.json();
const table = document.getElementById('settingsTable');
const tbody = table.querySelector('tbody');
if (data.properties) {
const keys = Object.keys(data.properties).sort();
tbody.innerHTML = keys.map(k => {
const isPassword = k.toLowerCase().includes('password') || k.toLowerCase().includes('rcon');
const inputType = isPassword ? 'password' : 'text';
const value = data.properties[k];
return `
<tr style="border-bottom: 1px solid #eee;">
<td style="padding: 12px; font-weight: bold;">${k}</td>
<td style="padding: 12px;">
<input
type="${inputType}"
value="${escapeHtml(value)}"
data-key="${k}"
onchange="updateSetting(this)"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; font-family: monospace;"
>
</td>
</tr>
`;
}).join('');
}
} catch (e) {
console.error('Erreur paramètres:', e);
}
}
async function updateSetting(input) {
const key = input.getAttribute('data-key');
const newValue = input.value;
try {
const response = await fetch(`${API_URL}/server/settings`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ key, value: newValue })
});
if (response.ok) {
showMessage(`✓ Paramètre ${key} modifié`, 'success', 'dashboardMessage');
} else {
const error = await response.json();
showMessage(`Erreur: ${error.error}`, 'error', 'dashboardMessage');
await loadSettingsData(); // Recharger pour annuler la modification
}
} catch (e) {
console.error('Erreur modification paramètre:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
}
}
// ========== ACTIONS ==========
async function restartServer() {
if (!confirm('⚠️ Redémarrer le serveur? Les joueurs seront déconnectés.')) return;
showMessage('Redémarrage du serveur...', 'success', 'dashboardMessage');
try {
const response = await fetch(`${API_URL}/rcon/restart`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({})
});
const data = await response.json();
if (response.ok) {
showMessage(data.message, 'success', 'dashboardMessage');
} else {
showMessage(data.error, 'error', 'dashboardMessage');
}
} catch (e) {
console.error('Erreur:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
}
}
async function saveServer() {
showMessage('Sauvegarde du serveur...', 'success', 'dashboardMessage');
try {
const response = await fetch(`${API_URL}/rcon/save`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({})
});
const data = await response.json();
if (response.ok) {
showMessage(data.message, 'success', 'dashboardMessage');
} else {
showMessage(data.error, 'error', 'dashboardMessage');
}
} catch (e) {
console.error('Erreur:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
}
}
function openChangeRconPasswordModal() {
const oldPassword = prompt('Ancien mot de passe RCON:');
if (!oldPassword) return;
const newPassword = prompt('Nouveau mot de passe RCON:');
if (!newPassword) return;
changeRconPassword(oldPassword, newPassword);
}
async function changeRconPassword(oldPassword, newPassword) {
try {
const response = await fetch(`${API_URL}/auth/change-rcon-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ oldPassword, newPassword })
});
const data = await response.json();
if (response.ok) {
showMessage(data.message, 'success', 'dashboardMessage');
} else {
showMessage(data.error, 'error', 'dashboardMessage');
}
} catch (e) {
console.error('Erreur:', e);
showMessage('Erreur serveur', 'error', 'dashboardMessage');
}
}
// ========== UTILITAIRES ==========
function showMessage(text, type, elementId) {
const element = document.getElementById(elementId);
if (element) {
element.textContent = text;
element.className = `message show ${type}`;
setTimeout(() => {
element.classList.remove('show');
}, 4000);
}
}
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
}
// Déconnexion
async function handleLogout() {
try {
await fetch(`${API_URL}/auth/logout`, {
method: 'POST',
credentials: 'include'
});
checkAuth();
} catch (e) {
console.error('Erreur:', e);
}
}