initial commit

This commit is contained in:
y.campiontrebouta@innotexnas.ovh
2026-02-04 19:04:46 +01:00
commit abb51904d7
27 changed files with 4011 additions and 0 deletions

9
frontend/package.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name": "nations-glory-admin-frontend",
"version": "1.0.0",
"description": "Frontend pour l'interface web de gestion Minecraft",
"private": true,
"scripts": {
"dev": "cd ../backend && npm run dev"
}
}

View File

@@ -0,0 +1,484 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #1e3c72;
--secondary: #2a5298;
--accent: #00d4ff;
--danger: #ff6b6b;
--success: #51cf66;
--warning: #fcc419;
--dark: #1a1a1a;
--light: #f8f9fa;
--text: #333;
--border: #ddd;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: var(--text);
min-height: 100vh;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* Login */
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
}
.login-box {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
width: 100%;
max-width: 400px;
}
.login-box h1 {
color: var(--primary);
margin-bottom: 30px;
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--text);
}
.form-group input {
width: 100%;
padding: 12px;
border: 1px solid var(--border);
border-radius: 5px;
font-size: 14px;
}
.form-group input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1);
}
button {
padding: 12px 24px;
border: none;
border-radius: 5px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
font-size: 14px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: white;
width: 100%;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(30, 60, 114, 0.4);
}
.btn-secondary {
background: var(--light);
color: var(--primary);
border: 2px solid var(--primary);
}
.btn-secondary:hover {
background: var(--primary);
color: white;
}
.btn-danger {
background: var(--danger);
color: white;
}
.btn-danger:hover {
opacity: 0.9;
}
.btn-success {
background: var(--success);
color: white;
}
.btn-warning {
background: var(--warning);
color: white;
}
.message {
padding: 12px 16px;
border-radius: 5px;
margin-bottom: 20px;
display: none;
}
.message.show {
display: block;
}
.message.success {
background: rgba(81, 207, 102, 0.1);
color: var(--success);
border-left: 4px solid var(--success);
}
.message.error {
background: rgba(255, 107, 107, 0.1);
color: var(--danger);
border-left: 4px solid var(--danger);
}
/* Dashboard */
.header {
background: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
color: var(--primary);
font-size: 28px;
}
.user-info {
display: flex;
align-items: center;
gap: 20px;
}
.user-info p {
color: var(--text);
}
/* Navigation */
.nav-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.nav-tabs button {
background: white;
border: 2px solid var(--border);
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
}
.nav-tabs button.active {
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: white;
border-color: transparent;
}
.nav-tabs button:hover:not(.active) {
border-color: var(--accent);
}
/* Content Sections */
.content-section {
display: none;
animation: fadeIn 0.3s;
}
.content-section.active {
display: block;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.section-card {
background: white;
padding: 25px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.section-card h2 {
color: var(--primary);
margin-bottom: 20px;
font-size: 20px;
}
/* Console */
.console-output {
background: #1e1e1e;
color: #00ff00;
padding: 15px;
border-radius: 5px;
font-family: 'Courier New', monospace;
height: 300px;
overflow-y: auto;
margin-bottom: 15px;
font-size: 12px;
border: 1px solid var(--border);
}
.console-output .error {
color: #ff6b6b;
}
.console-output .warning {
color: #fcc419;
}
.console-input {
display: flex;
gap: 10px;
}
.console-input input {
flex: 1;
}
/* Logs Viewer */
.logs-container {
background: #1e1e1e;
color: #00ff00;
padding: 15px;
border-radius: 5px;
font-family: 'Courier New', monospace;
height: 400px;
overflow-y: auto;
font-size: 12px;
border: 1px solid var(--border);
line-height: 1.5;
}
.logs-container .log-line {
margin-bottom: 2px;
}
.logs-container .error {
color: #ff6b6b;
}
.logs-container .warning {
color: #fcc419;
}
/* Tables */
table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
table thead {
background: var(--light);
}
table th {
padding: 12px;
text-align: left;
font-weight: 600;
color: var(--primary);
}
table td {
padding: 12px;
border-bottom: 1px solid var(--border);
}
table tbody tr:hover {
background: var(--light);
}
/* Buttons Group */
.btn-group {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.btn-group button {
margin: 0;
}
/* Grid */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.grid-card {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
text-align: center;
}
.grid-card h3 {
color: var(--primary);
margin-bottom: 10px;
}
.grid-card .value {
font-size: 24px;
font-weight: bold;
color: var(--secondary);
}
/* Modal */
.modal {
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;
}
.modal.show {
display: flex;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 10px;
width: 90%;
max-width: 500px;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
}
.modal-content h2 {
color: var(--primary);
margin-bottom: 20px;
}
.modal-content .form-group {
margin-bottom: 20px;
}
.modal-buttons {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
}
/* Loading spinner */
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(0,0,0,0.1);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Responsive */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header {
flex-direction: column;
gap: 15px;
text-align: center;
}
.nav-tabs {
flex-direction: column;
}
.nav-tabs button {
width: 100%;
}
.user-info {
flex-direction: column;
gap: 10px;
}
.login-box {
padding: 25px;
}
.grid {
grid-template-columns: 1fr;
}
.modal-content {
width: 95%;
}
}
/* Status indicators */
.status {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
vertical-align: middle;
}
.status.online {
background: var(--success);
}
.status.offline {
background: var(--danger);
}
.status.busy {
background: var(--warning);
}

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NationsGlory Admin Panel</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="app"></div>
<script src="js/app.js"></script>
</body>
</html>

889
frontend/public/js/app.js Normal file
View File

@@ -0,0 +1,889 @@
// Configuration API
const API_URL = 'http://localhost:3000/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>
<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>
</div>
</div>
`;
// Vérifier s'il y a déjà un admin
checkIfAdminExists();
}
// Vérifier si un admin existe
async function checkIfAdminExists() {
try {
const response = await fetch(`${API_URL}/auth/check`, {
credentials: 'include'
});
// Si pas connecté, afficher le formulaire d'enregistrement si c'est la première fois
setTimeout(() => {
const regForm = document.getElementById('registerForm');
const loginForm = document.getElementById('loginForm');
if (regForm && loginForm) {
// Pour la première connexion, montrer le formulaire d'enregistrement
regForm.style.display = 'block';
loginForm.style.display = 'none';
}
}, 100);
} catch (e) {
console.error('Erreur:', e);
}
}
// 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');
}
}
// 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</h3>
<div id="historyContainer" style="max-height: 300px; overflow-y: auto; background: #f8f9fa; border-radius: 5px; padding: 15px;"></div>
</div>
`;
}
async function loadConsoleData() {
try {
const response = await fetch(`${API_URL}/rcon/history`, {
credentials: 'include'
});
const history = await response.json();
const container = document.getElementById('historyContainer');
container.innerHTML = history.map(h => `
<div style="margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #ddd; font-size: 12px;">
<strong style="color: var(--primary);">${h.command}</strong><br>
<span style="color: #666;">${new Date(h.timestamp).toLocaleTimeString()}</span>
</div>
`).join('') || '<p style="color: #666;">Aucun historique</p>';
} catch (e) {
console.error('Erreur historique:', e);
}
}
async function sendRconCommand() {
const commandInput = document.getElementById('commandInput');
const command = commandInput.value.trim();
if (!command) return;
const output = document.getElementById('consoleOutput');
output.innerHTML += `<div>$ ${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>${data.response}</div>`;
showMessage('Commande exécutée', 'success', 'dashboardMessage');
} else {
output.innerHTML += `<div class="error">Erreur: ${data.error}</div>`;
showMessage(data.error, 'error', 'dashboardMessage');
}
output.scrollTop = output.scrollHeight;
} catch (e) {
console.error('Erreur envoi commande:', e);
output.innerHTML += `<div class="error">Erreur serveur</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'
});
const data = await response.json();
const table = document.getElementById('playersTable');
if (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</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);
}
}