Files
WebNationsGlory_ServeurBuil…/frontend/public/js/app.js
y.campiontrebouta@innotexnas.ovh ce25f7c93a 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
2026-02-04 21:58:42 +01:00

973 lines
35 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
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-success" onclick="restartServer()">🔄 Redémarrer</button>
<button class="btn-primary" onclick="saveServer()">💾 Sauvegarder</button>
<button class="btn-warning" onclick="openChangeRconPasswordModal()">🔐 Changer RCON</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">--</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 {
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>
`;
}
} 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 class="console-output" id="consoleOutput"></div>
<div class="console-input">
<input type="text" id="commandInput" placeholder="Entrez une commande..." onkeypress="if(event.key==='Enter') sendRconCommand();">
<button class="btn-primary" onclick="sendRconCommand()">Envoyer</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>
`;
}
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.innerHTML += `<div style="color: var(--primary); margin-bottom: 5px;">$ ${command}</div>`;
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) {
output.innerHTML += `<div style="color: #333; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #eee;">${data.response || '(pas de réponse)'}</div>`;
showMessage('Commande exécutée ✓', 'success', 'dashboardMessage');
loadConsoleData(); // Rafraîchir l'historique
} else {
output.innerHTML += `<div style="color: #dc3545; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #eee;">Erreur: ${data.error}</div>`;
showMessage(data.error, 'error', 'dashboardMessage');
}
output.scrollTop = output.scrollHeight;
} catch (e) {
console.error('Erreur envoi commande:', e);
output.innerHTML += `<div style="color: #dc3545; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #eee;">Erreur serveur: ${e.message}</div>`;
showMessage('Erreur serveur', 'error', '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 Connectés</h2>
<p style="margin-bottom: 15px; color: #666;">Liste des joueurs qui se sont connectés</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() {
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>`;
}
}
// ========== 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>
<table id="settingsTable">
<thead>
<tr>
<th>Paramètre</th>
<th>Valeur</th>
</tr>
</thead>
<tbody>
<tr><td colspan="2" style="text-align: center;">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 => `
<tr>
<td><strong>${k}</strong></td>
<td>${escapeHtml(data.properties[k])}</td>
</tr>
`).join('');
}
} catch (e) {
console.error('Erreur paramètres:', e);
}
}
// ========== 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);
}
}