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

15
.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
node_modules/
.env
.env.local
*.log
*.log.*
dist/
build/
.DS_Store
.idea/
.vscode/
*.swp
*.swo
*~
backups/
.web-admin/

318
CONFIGURATION.md Normal file
View File

@@ -0,0 +1,318 @@
# 🎮 Guide Complet - Interface Web NationsGlory Admin
## Étape 1: Préparation du Serveur Minecraft
### 1.1 Éditer server.properties
Localisez le fichier `server.properties` du serveur MC et assurez-vous que RCON est activé:
```properties
# Ligne 1: Activer RCON
enable-rcon=true
# Ligne 2: Port RCON (25575 par défaut)
rcon.port=25575
# Ligne 3: Mot de passe RCON (important!)
rcon.password=YourStrongPassword123
# Autres paramètres importants
motd=NationsGlory Server
gamemode=survival
difficulty=3
pvp=true
max-players=20
```
### 1.2 Redémarrer le Serveur MC
Redémarrez le serveur pour appliquer les changements RCON:
```bash
# Si le serveur tourne avec un script
./stop.sh
./start.sh
# Attendez qu'il redémarre complètement
```
Vérifiez que RCON est actif dans les logs du serveur (vous devriez voir quelque chose comme):
```
[Server thread/INFO]: RCON running on 0.0.0.0:25575
```
## Étape 2: Installation de l'Interface Web
### 2.1 Vérifier les Prérequis
```bash
# Vérifier Node.js
node --version # Doit être v14 ou plus
# Vérifier npm
npm --version # Doit être v6 ou plus
```
Si Node.js n'est pas installé:
- **Linux**: `sudo apt-get install nodejs npm`
- **macOS**: `brew install node`
- **Windows**: Télécharger depuis https://nodejs.org
### 2.2 Installation Automatique
```bash
cd /home/innotex/Documents/Projet/Serveur\ NationsGlory/WebNationsGlory_ServeurBuild_Red
chmod +x install.sh
./install.sh
```
Cela va:
- ✓ Vérifier Node.js
- ✓ Créer le fichier .env
- ✓ Installer les dépendances npm
## Étape 3: Configuration
### 3.1 Éditer le fichier .env
Ouvrez `backend/.env` avec votre éditeur préféré:
```env
# Mode d'exécution
NODE_ENV=development
# Port de l'application (8080, 3000, etc.)
PORT=3000
# Clé de session (générer une clé aléatoire forte)
SESSION_SECRET=abc123def456ghi789jkl012mno345pqr
# Chemin EXACT vers le dossier du serveur MC
SERVER_DIR=/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
# Paramètres RCON
RCON_HOST=localhost
RCON_PORT=25575
```
⚠️ **Important**:
- Vérifiez que `SERVER_DIR` est le **chemin exact**
- Le RCON_HOST peut être `localhost`, `127.0.0.1`, ou l'IP du serveur
- SESSION_SECRET doit être unique (utilisez une clé forte)
## Étape 4: Lancement
### 4.1 Démarrer l'Application
```bash
cd /home/innotex/Documents/Projet/Serveur\ NationsGlory/WebNationsGlory_ServeurBuild_Red
chmod +x start.sh
./start.sh
```
Vous devriez voir:
```
🚀 Backend Admin NationsGlory démarré sur http://localhost:3000
📁 Répertoire du serveur: /home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
```
### 4.2 Accéder à l'Interface
Ouvrez votre navigateur et allez à:
```
http://localhost:3000
```
## Étape 5: Premier Démarrage
### 5.1 Créer le Compte Admin
La première fois que vous accédez à l'interface:
1. **Tab "Enregistrement"** s'affiche
2. Remplissez:
- **Nom d'utilisateur**: Le nom que vous voulez pour vous connecter (ex: `admin`)
- **Mot de passe**: Un mot de passe fort
- **Pseudo Minecraft**: DOIT être un OP du serveur (ex: `VotreNomMC`)
3. Cliquez sur "Créer le compte"
⚠️ **Attention**: Le pseudo Minecraft doit être dans le fichier `ops.txt` ou `ops.json` du serveur!
### 5.2 Se Connecter
Après l'enregistrement:
1. Remplissez le formulaire de connexion
2. Entrez le nom d'utilisateur et mot de passe
3. Cliquez "Se connecter"
### 5.3 Premier Test
Une fois connecté:
1. Allez à l'onglet **Console RCON**
2. Entrez une commande simple: `/time query daytime`
3. Cliquez "Envoyer"
4. Vous devriez voir la réponse
Si ça marche, RCON est correctement configuré! ✓
## 🆘 Dépannage
### Problème: "Erreur RCON: Timeout"
**Cause**: Le serveur MC ne répond pas
**Solutions**:
1. Vérifiez que le serveur MC est en ligne
2. Vérifiez que RCON est activé dans server.properties
3. Vérifiez le port RCON (25575 par défaut)
4. Testez RCON avec un autre client:
```bash
telnet localhost 25575
```
### Problème: "Mot de passe RCON incorrect"
**Cause**: Le mot de passe ne correspond pas
**Solution**:
1. Vérifiez le mot de passe dans `server.properties`
2. Modifiez via l'interface: Dashboard → Changer RCON
3. Redémarrez le serveur MC après changement
### Problème: "Le joueur n'est pas OP sur le serveur"
**Cause**: Votre pseudo MC n'est pas OP
**Solution**:
1. Sur le serveur MC, dans la console:
```
op VotreNomMC
```
2. Attendez que le serveur se redémarre ou recharge les OPs
3. Réessayez l'enregistrement
### Problème: "Impossible de se connecter à localhost:3000"
**Cause**: L'application n'est pas lancée ou sur le mauvais port
**Solution**:
1. Vérifiez que `npm start` est lancé dans `backend/`
2. Vérifiez le PORT dans `.env`
3. Assurez-vous qu'aucune autre application n'utilise ce port:
```bash
lsof -i :3000 # Linux/Mac
netstat -ano | findstr :3000 # Windows
```
### Problème: "Node.js n'est pas trouvé"
**Solution**: Installer Node.js
- **Ubuntu/Debian**: `sudo apt-get install nodejs npm`
- **CentOS/RHEL**: `sudo yum install nodejs npm`
- **macOS**: `brew install node`
- **Windows**: https://nodejs.org
## 📊 Fonctionnalités Détaillées
### Console RCON
Exécutez n'importe quelle commande Minecraft:
```
/say Bienvenue!
/gamemode 1 @p
/give @p diamond 64
/weather clear
```
### Gestion des Logs
- Affichage temps réel
- Recherche par mot-clé
- Support multi-fichiers
### Whitelist
- Ajouter des joueurs
- Supprimer des joueurs
- Format JSON automatique
### Backups
- Création manuelle
- Compression tar.gz
- Gestion de l'espace
### Paramètres
- Lire tous les paramètres server.properties
- Modifier certains paramètres
- Changement de RCON
## 🔒 Sécurité - Important!
### En Production:
1. **Changez SESSION_SECRET**:
```bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
2. **Utilisez HTTPS**:
- Achetez un certificat SSL
- Configurez un proxy (nginx/Apache)
- Voir DEPLOYMENT.md
3. **Limitez l'accès**:
- Firewall: autoriser seulement vos IPs
- VPN: si accès distant
- Proxy avec authentification
4. **Changez RCON**:
- Utilisez un mot de passe fort
- Changez-le régulièrement
5. **Backups**:
- Sauvegardez vos backups ailleurs
- Testez les restaurations
## 📞 Support et Aide
### Vérification Rapide
```bash
# Vérifier que Node.js fonctionne
node --version
# Tester npm
npm --version
# Vérifier RCON du serveur MC
echo "status" | nc localhost 25575
# Vérifier les logs
cat /path/to/mc-server/latest.log | tail -50
```
### Logs de l'Application
Les logs du backend s'affichent en direct quand vous lancez `npm start`.
Logs des commandes RCON:
```
SERVER_DIR/.web-admin/rcon-history.json
```
## 🚀 Prochaines Étapes
- [ ] Tester toutes les fonctionnalités
- [ ] Ajouter des joueurs à la whitelist
- [ ] Créer des backups réguliers
- [ ] Configurer HTTPS pour la production
- [ ] Automatiser avec des scripts
## 📝 Notes Importantes
1. **Redémarrage du serveur**: Les joueurs seront déconnectés
2. **Sauvegarde**: Faites des backups réguliers
3. **RCON**: Gardez le mot de passe sécurisé
4. **Logs**: Vérifiez régulièrement pour les erreurs
5. **Mises à jour**: Vérifiez les mises à jour Node.js
Bon courage avec votre serveur! 🎮

64
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,64 @@
# Proxy Configuration pour Production
# Utilisez ce fichier comme base pour configurer votre serveur web
# NGINX Configuration Example
# ==========================
# server {
# listen 80;
# server_name admin.nationglory.com;
#
# # Redirect to HTTPS
# return 301 https://$server_name$request_uri;
# }
#
# server {
# listen 443 ssl http2;
# server_name admin.nationglory.com;
#
# ssl_certificate /etc/ssl/certs/your-cert.crt;
# ssl_certificate_key /etc/ssl/private/your-key.key;
#
# client_max_body_size 100M;
#
# location / {
# proxy_pass http://localhost:3000;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection 'upgrade';
# proxy_set_header Host $host;
# proxy_cache_bypass $http_upgrade;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# }
# }
# Apache Configuration Example
# ============================
# <VirtualHost *:80>
# ServerName admin.nationglory.com
# Redirect permanent / https://admin.nationglory.com/
# </VirtualHost>
#
# <VirtualHost *:443>
# ServerName admin.nationglory.com
# SSLEngine on
# SSLCertificateFile /etc/ssl/certs/your-cert.crt
# SSLCertificateKeyFile /etc/ssl/private/your-key.key
#
# ProxyPreserveHost On
# ProxyPass / http://localhost:3000/
# ProxyPassReverse / http://localhost:3000/
#
# RequestHeader set X-Forwarded-Proto "https"
# RequestHeader set X-Forwarded-Port "443"
# </VirtualHost>
# Environment Variables
# ======================
# Set these in production:
# NODE_ENV=production
# SESSION_SECRET=use-a-strong-random-key-here
# PORT=3000 (internal, proxy on 80/443)

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM node:18-alpine
WORKDIR /app
COPY backend/package*.json ./
RUN npm ci --only=production
COPY backend/src ./src
COPY frontend ./frontend
EXPOSE 3000
CMD ["node", "src/server.js"]

244
INDEX.md Normal file
View File

@@ -0,0 +1,244 @@
# 📋 Index - Fichiers et Documentation
## 🗂️ Structure du Projet
```
WebNationsGlory_ServeurBuild_Red/
├── 📄 Documentation
│ ├── README.md ← Vue d'ensemble générale
│ ├── QUICKSTART.md ← Démarrage rapide (5 min)
│ ├── CONFIGURATION.md ← Configuration détaillée
│ ├── MAINTENANCE.md ← Commandes et troubleshooting
│ ├── DEPLOYMENT.md ← Production & HTTPS
│ └── INDEX.md ← Ce fichier
├── 🚀 Scripts de Démarrage
│ ├── setup.sh ← Préparation initiale
│ ├── install.sh ← Installation des dépendances
│ ├── start.sh ← Lancer l'application
│ └── .gitignore ← Fichiers à ignorer Git
├── 📦 Backend (Express.js - Node.js)
│ ├── package.json ← Dépendances Node.js
│ ├── .env.example ← Template de configuration
│ └── src/
│ ├── server.js ← Point d'entrée principal
│ ├── routes/
│ │ ├── auth.js ← Authentification & login
│ │ ├── rcon.js ← Console RCON & redémarrage
│ │ ├── logs.js ← Gestion des logs
│ │ ├── players.js ← Liste des joueurs
│ │ ├── whitelist.js ← Gestion whitelist
│ │ ├── backup.js ← Création de backups
│ │ └── server.js ← Paramètres serveur
│ └── utils/
│ └── rcon.js ← Client RCON (protocole)
├── 🎨 Frontend (HTML/CSS/JavaScript)
│ ├── package.json ← Meta frontend
│ └── public/
│ ├── index.html ← Page HTML principale
│ ├── css/
│ │ └── style.css ← Tous les styles
│ └── js/
│ └── app.js ← Application React-like
├── 🐳 Deployment
│ ├── Dockerfile ← Build Docker
│ └── docker-compose.yml ← Orchestration Docker
└── 🔧 Configuration
├── .gitignore
├── docker-compose.yml
└── Dockerfile
```
## 📖 Guides de Lecture
### Pour Démarrer Rapidement
1. **QUICKSTART.md** - 5 minutes pour être opérationnel
2. Suivez les 5 étapes simples
### Pour Comprendre la Configuration
1. **README.md** - Vue d'ensemble
2. **CONFIGURATION.md** - Configuration détaillée
3. Éditer les fichiers .env
### Pour Utiliser l'Application
1. Ouvrir http://localhost:3000
2. Créer le compte admin
3. Utiliser les onglets du dashboard
### Pour Administrer le Serveur
1. **MAINTENANCE.md** - Commandes utiles
2. Sauvegardes régulières
3. Monitoring
### Pour la Production
1. **DEPLOYMENT.md** - Configuration HTTPS/Proxy
2. Sécurité renforcée
3. Certificats SSL
## 🔑 Points Clés
### Configuration
- **Backend .env**: Variables d'environnement critiques
- **Server.properties**: RCON du serveur MC
- **Ops.txt**: Liste des administrateurs
### Sécurité
- SESSION_SECRET unique en production
- HTTPS recommandé
- Firewall bien configuré
### Maintenance
- Backups réguliers
- Logs à surveiller
- Mises à jour Node.js
## 🚀 Quickstart Ultime
```bash
# 1. Configuration serveur MC
# Éditer server.properties:
enable-rcon=true
rcon.port=25575
rcon.password=YourPassword123
# 2. Installation
cd WebNationsGlory_ServeurBuild_Red
./install.sh
# 3. Configuration app
nano backend/.env
# 4. Lancement
./start.sh
# 5. Accès
# Ouvrir: http://localhost:3000
```
## 📚 Fichiers Importants à Connaître
| Fichier | Rôle | Éditer? |
|---------|------|--------|
| backend/.env | Configuration | ✏️ OUI |
| backend/src/server.js | Point d'entrée | ❌ Non |
| frontend/public/js/app.js | Interface | ❌ Non (sauf custom) |
| README.md | Documentation | ✏️ Opcional |
| CONFIGURATION.md | Guide détaillé | ❌ Non |
## 🎯 Fonctionnalités par Fichier
### Routes API
**auth.js** 🔐
- Création compte admin
- Connexion/Déconnexion
- Changement mot de passe RCON
**rcon.js** ⌨️
- Exécution commandes RCON
- Historique commandes
- Redémarrage serveur
- Sauvegarde monde
**logs.js** 📜
- Visualisation logs
- Recherche dans logs
- Gestion fichiers logs
**players.js** 👥
- Liste joueurs
- UUID et connexions
- Statistiques
**whitelist.js**
- Ajouter joueurs
- Retirer joueurs
- Gestion format
**backup.js** 💾
- Créer backups
- Lister backups
- Supprimer backups
**server.js** ⚙️
- Lire paramètres
- Modifier paramètres
- Configuration globale
### Utils
**rcon.js** 🔌
- Client RCON (protocole Minecraft)
- Connection/Auth
- Envoi commandes
- Parsing réponses
## 🔄 Flux de Requête
```
Browser (frontend/public/js/app.js)
HTTP Request
Backend Express (backend/src/server.js)
Route Handler (backend/src/routes/*.js)
RCON Client (backend/src/utils/rcon.js)
Serveur Minecraft
Response → Backend → Browser
```
## 🎓 Pour Apprendre
- **JavaScript**: app.js (client-side)
- **Node.js**: server.js (backend)
- **Express**: routes/*.js (API)
- **RCON Protocol**: utils/rcon.js
- **HTML/CSS**: index.html + style.css
## 🚨 Fichiers Critiques
Ne pas modifier sans savoir:
- ✓ backend/src/utils/rcon.js - Protocole RCON
- ✓ backend/src/server.js - Point d'entrée
- ✓ backend/src/routes/auth.js - Sécurité
Peuvent être modifiés:
- ✏️ frontend/public/css/style.css - Design
- ✏️ frontend/public/js/app.js - Interface
- ✏️ backend/.env - Configuration
## 📞 Ressources
- **Node.js Docs**: https://nodejs.org/docs
- **Express Docs**: https://expressjs.com
- **Minecraft Wiki RCON**: https://wiki.vg/RCON
- **JavaScript MDN**: https://developer.mozilla.org
## ✅ Checklist d'Installation
- [ ] Node.js 14+ installé
- [ ] ./install.sh exécuté
- [ ] backend/.env configuré
- [ ] Serveur MC avec RCON activé
- [ ] Premier compte admin créé
- [ ] Test RCON validé
- [ ] Documentations lues
## 🎉 Vous êtes Prêt!
Profitez de votre interface d'administration! 🚀
Pour plus d'aide, consultez:
1. QUICKSTART.md (5 min)
2. CONFIGURATION.md (détails)
3. MAINTENANCE.md (commandes)
4. DEPLOYMENT.md (production)

347
MAINTENANCE.md Normal file
View File

@@ -0,0 +1,347 @@
# 🔧 Maintenance et Administration
## Commandes de Base
### Démarrer l'application
```bash
cd WebNationsGlory_ServeurBuild_Red
./start.sh
```
### Arrêter l'application
```bash
# Ctrl+C dans le terminal
```
### Redémarrer l'application
```bash
# 1. Arrêtez avec Ctrl+C
# 2. Relancez avec ./start.sh
```
## Dépannage
### Vérifier l'état de RCON
```bash
# Test simple
echo "status" | timeout 2 bash -c 'exec 3<>/dev/tcp/localhost/25575; cat >&3; cat <&3' 2>/dev/null || echo "RCON non accessible"
# Avec nc (netcat)
nc -w 2 localhost 25575 < /dev/null && echo "RCON OK" || echo "RCON KO"
# Avec telnet
telnet localhost 25575
```
### Vérifier les logs du serveur MC
```bash
# Dernières 50 lignes
tail -50 /path/to/mc-server/latest.log
# Rechercher les erreurs
grep ERROR /path/to/mc-server/latest.log
# Suivre en temps réel
tail -f /path/to/mc-server/latest.log
```
### Vérifier si le port 3000 est utilisé
```bash
# Linux/Mac
lsof -i :3000
# Windows PowerShell
Get-Process -Id (Get-NetTCPConnection -LocalPort 3000).OwningProcess
# Tuer le processus (Linux/Mac)
kill -9 $(lsof -t -i :3000)
```
### Réinitialiser Node.js
```bash
cd backend
# Supprimer node_modules
rm -rf node_modules
rm package-lock.json
# Réinstaller
npm install
# Redémarrer
npm start
```
## Gestion des OPs
### Ajouter un OP sur le serveur MC
```
/op NomdujoueurMC
```
### Vérifier les OPs
```bash
# Fichier ops.txt
cat /path/to/mc-server/ops.txt
# Ou ops.json (Minecraft 1.8+)
cat /path/to/mc-server/ops.json
```
## Backups
### Créer un backup manuel
```bash
cd /path/to/mc-server
# Créer un tar.gz du serveur
tar -czf ../backup-$(date +%Y%m%d-%H%M%S).tar.gz .
# Avec exclusion de certains fichiers
tar -czf ../backup-$(date +%Y%m%d-%H%M%S).tar.gz \
--exclude='*.log' \
--exclude='cache' \
--exclude='.web-admin' \
.
```
### Restaurer un backup
```bash
# Arrêter le serveur d'abord!
./stop.sh
# Restaurer
cd /backup/location
tar -xzf backup-file.tar.gz -C /path/to/mc-server
# Redémarrer
cd /path/to/mc-server
./start.sh
```
## Logs et Monitoring
### Voir les logs backend en temps réel
```bash
cd backend
npm start
```
### Voir les logs RCON (historique des commandes)
```bash
cat /path/to/mc-server/.web-admin/rcon-history.json
```
### Nettoyer les vieux logs
```bash
# Supprimer les logs de plus de 30 jours
find /path/to/mc-server -name "*.log" -mtime +30 -delete
```
## Variables d'Environnement
### Changer le port
```env
# backend/.env
PORT=8080
```
### Changer le répertoire du serveur
```env
# backend/.env
SERVER_DIR=/path/to/other/mc-server
```
### Changer les identifiants RCON
```env
# backend/.env
RCON_HOST=192.168.1.100
RCON_PORT=25576
```
## Mise à Jour des Dépendances
### Vérifier les mises à jour disponibles
```bash
cd backend
npm outdated
```
### Mettre à jour les dépendances
```bash
cd backend
npm update
```
### Mettre à jour une dépendance spécifique
```bash
cd backend
npm install express@latest
```
## Sécurité
### Changer le SESSION_SECRET
```bash
# Générer une nouvelle clé
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Mettre à jour dans backend/.env
SESSION_SECRET=<nouvelle-cle-generee>
# Redémarrer l'application
```
### Changer le mot de passe RCON
```bash
# Via l'interface web: Dashboard → Changer RCON
# Ou éditer manuellement server.properties:
rcon.password=NewPassword123
# Redémarrer le serveur MC
```
## Performance
### Vérifier l'utilisation mémoire Node.js
```bash
ps aux | grep node
# Linux avec plus de détails
ps -eo pid,vsz,rss,comm | grep node
```
### Nettoyer les fichiers temporaires
```bash
cd backend
rm -rf node_modules
npm install
```
## Troubleshooting
### L'application démarre mais l'interface ne charge pas
```bash
# Vérifier que le frontend est bien servi
curl http://localhost:3000/
# Doit retourner l'HTML
# Vérifier la console du navigateur (F12) pour les erreurs
```
### Les commandes RCON n'exécutent pas
```bash
# Vérifier que RCON est activé
grep "enable-rcon" /path/to/mc-server/server.properties
# Doit être: enable-rcon=true
# Vérifier le mot de passe RCON
grep "rcon.password" /path/to/mc-server/server.properties
# Tester RCON directement
echo "say Test" | nc localhost 25575
```
### Base de données de sessions corruptée
```bash
# Supprimer les sessions
rm -rf backend/sessions/*
# Redémarrer l'application
./start.sh
```
## Commandes Minecraft Utiles
### Via la console web
```
/list # Liste les joueurs
/say @a Message # Message à tous
/tp @p @s # Téléporter
/weather clear # Météo
/time set day # Heure
/gamerule <règle> # Game rules
/difficulty 3 # Difficulté
/seed # Seed du monde
/spawnpoint @p 0 100 0 # Spawnpoint
```
## Scripts Automatisés
### Script de backup automatique
```bash
#!/bin/bash
# save-mc-backup.sh
SERVER_DIR="/path/to/mc-server"
BACKUP_DIR="$SERVER_DIR/backups"
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.tar.gz"
cd "$SERVER_DIR"
tar -czf "$BACKUP_FILE" \
--exclude='*.log' \
--exclude='cache' \
--exclude='.web-admin' \
.
echo "Backup créé: $BACKUP_FILE"
# À ajouter à crontab:
# 0 2 * * * /path/to/save-mc-backup.sh
```
### Script de redémarrage programmé
```bash
#!/bin/bash
# restart-mc-server.sh
echo "Redémarrage du serveur MC dans 1 minute..."
# Via RCON
echo "say Redémarrage dans 1 minute!" | nc localhost 25575
sleep 30
echo "say 30 secondes!" | nc localhost 25575
sleep 20
echo "say 10 secondes!" | nc localhost 25575
sleep 10
echo "stop" | nc localhost 25575
sleep 5
# Redémarrer
cd /path/to/mc-server
./start.sh
```
## Monitoring
### Vérifier l'espace disque
```bash
# Espace utilisé par le serveur
du -sh /path/to/mc-server
# Espace libre
df -h /path/to/mc-server
```
### Vérifier les processus Node.js
```bash
ps aux | grep node
# Ou
ps aux | grep npm
```
### Vérifier les ports en écoute
```bash
netstat -tlnp | grep 3000 # Node.js
netstat -tlnp | grep 25575 # RCON
netstat -tlnp | grep 25565 # Serveur MC
```
---
**💡 Astuce**: Sauvegardez ces commandes pour un accès rapide!

136
QUICKSTART.md Normal file
View File

@@ -0,0 +1,136 @@
# 🎯 Guide Rapide - Démarrage en 5 Minutes
## Prérequis
- Node.js 14+ installé
- Serveur Minecraft avec RCON activé
- Vous devez être OP sur le serveur
## ⚡ Installation Express
### 1⃣ Configuration du serveur MC (10 secondes)
Éditer le fichier `server.properties` du serveur Minecraft:
```properties
enable-rcon=true
rcon.port=25575
rcon.password=YourPassword123
```
Redémarrer le serveur MC.
### 2⃣ Installation de l'app (2 minutes)
```bash
cd /home/innotex/Documents/Projet/Serveur\ NationsGlory/WebNationsGlory_ServeurBuild_Red
# Installation automatique
./install.sh
```
### 3⃣ Configuration (1 minute)
```bash
# Éditer le fichier
nano backend/.env
```
Vérifiez:
```env
PORT=3000
SERVER_DIR=/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
RCON_HOST=localhost
RCON_PORT=25575
```
### 4⃣ Lancement (30 secondes)
```bash
./start.sh
```
Vous devriez voir:
```
🚀 Backend Admin NationsGlory démarré sur http://localhost:3000
```
### 5⃣ Accès (10 secondes)
Ouvrez: **http://localhost:3000**
## 👤 Premier Compte
1. Remplissez le formulaire d'enregistrement:
- Nom d'utilisateur: `admin` (ou ce que vous voulez)
- Mot de passe: Votre mot de passe
- Pseudo Minecraft: **VOTRE NOM DE JOUEUR** (doit être OP!)
2. Cliquez "Créer le compte"
3. Connectez-vous avec vos identifiants
## ✅ Test Rapide
1. Allez à l'onglet **Console RCON**
2. Tapez: `/time query daytime`
3. Cliquez "Envoyer"
4. Vous devriez voir l'heure du serveur
Si ça marche = ✓ Tout est bon!
## 🎮 Commandes Utiles
```
/say Bienvenue! → Message à tous
/tp @s 0 100 0 → Se téléporter
/give @p diamond 64 → Donner des items
/weather clear → Météo
/difficulty 3 → Difficulté
/time set day → Midi
```
## ⚠️ Problèmes Courants
| Problème | Solution |
|----------|----------|
| "Timeout RCON" | Vérifier que le serveur MC est en ligne |
| "Mot de passe incorrect" | Vérifier dans server.properties |
| "Joueur n'est pas OP" | Faire `/op NomDuJoueur` sur le serveur MC |
| "Impossible de se connecter" | Vérifier que `npm start` est lancé |
## 📁 Fichiers Importants
```
backend/
├── .env ← Configuration (à modifier)
├── package.json ← Dépendances
└── src/
├── server.js ← Point d'entrée
└── routes/ ← API endpoints
frontend/
└── public/
└── index.html ← Interface web
```
## 🔐 Sécurité Basique
1. Changez `SESSION_SECRET` dans `.env` (ligne importante)
2. Utilisez un mot de passe RCON fort
3. Ne partagez pas vos identifiants
## 📖 Documentation Complète
- **README.md** - Vue d'ensemble complète
- **CONFIGURATION.md** - Configuration détaillée
- **DEPLOYMENT.md** - Déploiement en production
## 🆘 Besoin d'aide?
1. Vérifiez les logs: `npm start` affiche tout
2. Testez RCON: `echo "status" | nc localhost 25575`
3. Lisez CONFIGURATION.md pour plus de détails
---
**Vous êtes prêt!** 🚀 Profitez de votre interface!

228
README.md Normal file
View File

@@ -0,0 +1,228 @@
# Interface Web d'Administration - NationsGlory
Application web complète pour gérer votre serveur Minecraft 1.6.4 sans taper une seule commande!
## 🚀 Fonctionnalités
### 🔐 Sécurité
- Authentification sécurisée par mot de passe
- Seuls les OPs du serveur MC peuvent se connecter
- Sessions sécurisées avec expiration
### ⌨️ Console RCON
- Exécutez des commandes directement depuis l'interface
- Historique complet des commandes
- Réponses en temps réel
### 📊 Dashboard
- État du serveur en temps réel
- Nombre de joueurs connectés
- Informations rapides du serveur
### 📜 Logs
- Visualisation des logs en temps réel
- Recherche dans les logs
- Plusieurs fichiers logs supportés
### 👥 Gestion des Joueurs
- Liste de tous les joueurs ayant joué
- UUID et dernière connexion
- Informations détaillées
### ✅ Whitelist
- Ajouter/supprimer des joueurs
- Gestion complète de la whitelist
- Support des formats JSON et TXT
### 💾 Backups
- Création de backups automatiques
- Gestion des anciens backups
- Suppression facile
### ⚙️ Paramètres
- Modification des settings du serveur
- Changement du mot de passe RCON
- Gestion complète de server.properties
### 🔄 Commandes Utiles
- Redémarrage du serveur
- Sauvegarde du monde
- Notifications aux joueurs
## 📋 Prérequis
- Node.js 14+ et npm
- Serveur Minecraft avec RCON activé
- RCON correctement configuré dans server.properties
## 🔧 Installation
### 1. Préparer le serveur Minecraft
Éditer `server.properties` du serveur MC:
```properties
enable-rcon=true
rcon.port=25575
rcon.password=votreMotDePasseRcon
```
Redémarrer le serveur MC.
### 2. Installer l'interface web
```bash
cd WebNationsGlory_ServeurBuild_Red
chmod +x install.sh
./install.sh
```
### 3. Configurer
Éditer `backend/.env`:
```env
NODE_ENV=development
PORT=3000
SESSION_SECRET=changez-cette-cle-en-production
SERVER_DIR=/path/to/NationsGlory_ServeurBuild_Red
RCON_HOST=localhost
RCON_PORT=25575
```
### 4. Lancer l'application
```bash
chmod +x start.sh
./start.sh
```
L'interface est maintenant accessible sur: **http://localhost:3000**
## 🎯 Premier Démarrage
1. Ouvrez http://localhost:3000
2. Créez le compte admin (doit être un OP du serveur)
3. Connectez-vous
4. Explorez l'interface!
## 📁 Structure du Projet
```
WebNationsGlory_ServeurBuild_Red/
├── backend/
│ ├── package.json
│ ├── .env (à créer)
│ └── src/
│ ├── server.js (point d'entrée)
│ ├── routes/
│ │ ├── auth.js (authentification)
│ │ ├── rcon.js (console & contrôle)
│ │ ├── logs.js (visualisation logs)
│ │ ├── players.js (gestion joueurs)
│ │ ├── whitelist.js (gestion whitelist)
│ │ ├── backup.js (gestion backups)
│ │ └── server.js (paramètres)
│ └── utils/
│ └── rcon.js (client RCON)
├── frontend/
│ └── public/
│ ├── index.html
│ ├── js/app.js
│ └── css/style.css
└── start.sh
```
## 🔌 API Endpoints
### Authentification
- `POST /api/auth/register` - Créer un compte admin
- `POST /api/auth/login` - Se connecter
- `GET /api/auth/check` - Vérifier la connexion
- `POST /api/auth/logout` - Se déconnecter
### Console RCON
- `POST /api/rcon/command` - Exécuter une commande
- `GET /api/rcon/history` - Historique des commandes
- `POST /api/rcon/restart` - Redémarrer le serveur
- `POST /api/rcon/save` - Sauvegarder
### Serveur
- `GET /api/server` - Récupérer les paramètres
- `POST /api/server/update` - Mettre à jour un paramètre
### Logs
- `GET /api/logs` - Récupérer les logs
- `GET /api/logs/files` - Lister les fichiers logs
- `GET /api/logs/file/:filename` - Lire un fichier spécifique
- `GET /api/logs/search?query=...` - Chercher dans les logs
### Joueurs
- `GET /api/players` - Lister les joueurs
### Whitelist
- `GET /api/whitelist` - Récupérer la whitelist
- `POST /api/whitelist/add` - Ajouter un joueur
- `POST /api/whitelist/remove` - Retirer un joueur
### Backups
- `GET /api/backup` - Lister les backups
- `POST /api/backup/create` - Créer un backup
- `POST /api/backup/delete/:filename` - Supprimer un backup
## 🆘 Dépannage
### La connexion RCON échoue
- Vérifiez que RCON est activé dans server.properties
- Vérifiez le port et le mot de passe
- Assurez-vous que le serveur MC est en ligne
### Les logs ne s'affichent pas
- Vérifiez le chemin de SERVER_DIR dans .env
- Assurez-vous que les fichiers logs existent
### Les joueurs ne s'affichent pas
- Attendez qu'au moins un joueur se soit connecté
- Vérifiez que usercache.json existe
## ⚠️ Sécurité
En production:
- Changez `SESSION_SECRET` avec une clé forte
- Utilisez HTTPS
- Placez derrière un proxy (nginx, Apache)
- Limitez l'accès par IP si possible
## 📝 Logs Applicatifs
Les logs de l'application se trouvent dans:
- Backend console: directement dans le terminal
- RCON historique: `SERVER_DIR/.web-admin/rcon-history.json`
## 🎮 Commandes Minecraft Utiles
Depuis la console RCON:
- `/say Message` - Envoyer un message à tous
- `/tp @p @s` - Tp un joueur
- `/give @p diamond 64` - Donner des items
- `/weather clear` - Changer la météo
- `/time set day` - Régler l'heure
- `/difficulty 2` - Changer la difficulté
## 📞 Support
Pour les problèmes:
1. Vérifiez la configuration du .env
2. Vérifiez les logs du backend
3. Testez RCON avec un client externe
4. Vérifiez les permissions sur les fichiers du serveur
## 🔄 Mises à Jour
L'interface se met à jour automatiquement. Pour mettre à jour les dépendances:
```bash
cd backend
npm update
```
## 📄 License
MIT - Libre d'utilisation

6
backend/.env.example Normal file
View File

@@ -0,0 +1,6 @@
NODE_ENV=development
PORT=3000
SESSION_SECRET=your-very-secret-session-key-change-in-production
SERVER_DIR=/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
RCON_HOST=localhost
RCON_PORT=25575

25
backend/package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "nations-glory-admin-backend",
"version": "1.0.0",
"description": "Backend API pour l'interface web de gestion du serveur Minecraft NationsGlory",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js"
},
"keywords": ["minecraft", "admin", "api"],
"author": "Admin",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"express-session": "^1.17.3",
"bcryptjs": "^2.4.3",
"dotenv": "^16.0.3",
"cors": "^2.8.5",
"multer": "^1.4.5-lts.1",
"fs-extra": "^11.1.0"
},
"devDependencies": {
"nodemon": "^2.0.20"
}
}

200
backend/src/routes/auth.js Normal file
View File

@@ -0,0 +1,200 @@
const express = require('express');
const bcrypt = require('bcryptjs');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
const USERS_FILE = path.join(__dirname, '../../data/users.json');
async function initUsersFile() {
await fs.ensureDir(path.dirname(USERS_FILE));
if (!await fs.pathExists(USERS_FILE)) {
await fs.writeJson(USERS_FILE, [], { spaces: 2 });
}
}
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
async function getServerOps() {
const opsFile = path.join(SERVER_DIR, 'ops.txt');
try {
if (await fs.pathExists(opsFile)) {
const content = await fs.readFile(opsFile, 'utf-8');
return content
.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'));
}
} catch (e) {
console.error('Erreur lecture ops.txt:', e);
}
return [];
}
async function getServerOpsJson() {
const opsFile = path.join(SERVER_DIR, 'ops.json');
try {
if (await fs.pathExists(opsFile)) {
const ops = await fs.readJson(opsFile);
return ops.map(op => typeof op === 'string' ? op : op.name).filter(Boolean);
}
} catch (e) {
console.error('Erreur lecture ops.json:', e);
}
return [];
}
router.post('/register', async (req, res) => {
try {
await initUsersFile();
const users = await fs.readJson(USERS_FILE);
if (users.length > 0) {
return res.status(403).json({ error: 'Enregistrement déjà effectué' });
}
const { username, password, mcUsername } = req.body;
if (!username || !password || !mcUsername) {
return res.status(400).json({ error: 'Données manquantes' });
}
const serverOps = await getServerOps();
const serverOpsJson = await getServerOpsJson();
const allOps = [...new Set([...serverOps, ...serverOpsJson])];
if (!allOps.includes(mcUsername)) {
return res.status(403).json({
error: 'Le joueur n\'est pas OP sur le serveur',
availableOps: allOps
});
}
const hashedPassword = await bcrypt.hash(password, 10);
users.push({
id: Date.now().toString(),
username,
mcUsername,
password: hashedPassword,
createdAt: new Date()
});
await fs.writeJson(USERS_FILE, users, { spaces: 2 });
res.json({
message: 'Admin créé avec succès',
user: { username, mcUsername }
});
} catch (error) {
console.error('Erreur enregistrement:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/login', async (req, res) => {
try {
await initUsersFile();
const users = await fs.readJson(USERS_FILE);
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: 'Identifiants manquants' });
}
const user = users.find(u => u.username === username);
if (!user) {
return res.status(401).json({ error: 'Identifiants incorrects' });
}
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
return res.status(401).json({ error: 'Identifiants incorrects' });
}
req.session.user = {
id: user.id,
username: user.username,
mcUsername: user.mcUsername
};
res.json({
message: 'Connecté avec succès',
user: req.session.user
});
} catch (error) {
console.error('Erreur connexion:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.get('/check', (req, res) => {
if (req.session.user) {
res.json({ authenticated: true, user: req.session.user });
} else {
res.json({ authenticated: false });
}
});
router.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Erreur déconnexion' });
}
res.json({ message: 'Déconnecté' });
});
});
router.post('/change-rcon-password', isAuthenticated, async (req, res) => {
try {
const { oldPassword, newPassword } = req.body;
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
if (!oldPassword || !newPassword) {
return res.status(400).json({ error: 'Données manquantes' });
}
let content = await fs.readFile(serverPropsFile, 'utf-8');
const lines = content.split('\n');
let currentPassword = null;
for (let line of lines) {
if (line.startsWith('rcon.password=')) {
currentPassword = line.split('=')[1].trim();
break;
}
}
if (currentPassword !== oldPassword) {
return res.status(401).json({ error: 'Ancien mot de passe RCON incorrect' });
}
content = lines.map(line => {
if (line.startsWith('rcon.password=')) {
return `rcon.password=${newPassword}`;
}
return line;
}).join('\n');
await fs.writeFile(serverPropsFile, content, 'utf-8');
res.json({ message: 'Mot de passe RCON changé (redémarrage du serveur requis)' });
} catch (error) {
console.error('Erreur changement RCON:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

View File

@@ -0,0 +1,99 @@
const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const { exec } = require('child_process');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
router.get('/', isAuthenticated, async (req, res) => {
try {
const backupDir = path.join(SERVER_DIR, 'backups');
if (!await fs.pathExists(backupDir)) {
return res.json({ backups: [] });
}
const files = await fs.readdir(backupDir);
const backups = await Promise.all(
files.map(async (file) => {
const filePath = path.join(backupDir, file);
const stats = await fs.stat(filePath);
return {
name: file,
size: (stats.size / (1024 * 1024)).toFixed(2) + ' MB',
created: stats.mtime,
path: filePath
};
})
);
res.json(backups.sort((a, b) => new Date(b.created) - new Date(a.created)));
} catch (error) {
console.error('Erreur backups:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/create', isAuthenticated, async (req, res) => {
try {
const backupDir = path.join(SERVER_DIR, 'backups');
await fs.ensureDir(backupDir);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupName = `backup-${timestamp}.tar.gz`;
const backupPath = path.join(backupDir, backupName);
res.json({ message: 'Backup en cours...', backupName });
// Exécuter la création de backup en arrière-plan
exec(
`cd "${SERVER_DIR}" && tar --exclude='.web-admin' --exclude='*.log.lck' -czf "${backupPath}" . 2>/dev/null`,
(error) => {
if (error) {
console.error('Erreur création backup:', error);
} else {
console.log(`✓ Backup créé: ${backupName}`);
}
}
);
} catch (error) {
console.error('Erreur création backup:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/delete/:filename', isAuthenticated, async (req, res) => {
try {
const { filename } = req.params;
if (filename.includes('..')) {
return res.status(400).json({ error: 'Accès non autorisé' });
}
const backupPath = path.join(SERVER_DIR, 'backups', filename);
if (!await fs.pathExists(backupPath)) {
return res.status(404).json({ error: 'Fichier non trouvé' });
}
await fs.remove(backupPath);
res.json({ message: 'Backup supprimé' });
} catch (error) {
console.error('Erreur suppression backup:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

138
backend/src/routes/logs.js Normal file
View File

@@ -0,0 +1,138 @@
const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
router.get('/', isAuthenticated, async (req, res) => {
try {
const { lines = 100 } = req.query;
const logsDir = SERVER_DIR;
let logFile = path.join(logsDir, 'latest.log');
if (!await fs.pathExists(logFile)) {
logFile = path.join(logsDir, 'ForgeModLoader-server-0.log');
}
if (!await fs.pathExists(logFile)) {
return res.json({ logs: [], message: 'Aucun fichier log trouvé' });
}
const content = await fs.readFile(logFile, 'utf-8');
const logLines = content.split('\n');
const lastLines = logLines.slice(-parseInt(lines));
res.json({
logs: lastLines,
file: path.basename(logFile),
totalLines: logLines.length
});
} catch (error) {
console.error('Erreur logs:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.get('/files', isAuthenticated, async (req, res) => {
try {
const logsDir = SERVER_DIR;
const files = await fs.readdir(logsDir);
const logFiles = files.filter(f =>
f.includes('log') || f.includes('Log')
);
const filesWithStats = await Promise.all(
logFiles.map(async (f) => {
const stats = await fs.stat(path.join(logsDir, f));
return {
name: f,
size: (stats.size / 1024).toFixed(2) + ' KB',
modified: stats.mtime
};
})
);
res.json(filesWithStats.sort((a, b) => new Date(b.modified) - new Date(a.modified)));
} catch (error) {
console.error('Erreur liste logs:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.get('/file/:filename', isAuthenticated, async (req, res) => {
try {
const { filename } = req.params;
const { lines = 100 } = req.query;
if (filename.includes('..')) {
return res.status(400).json({ error: 'Accès non autorisé' });
}
const filePath = path.join(SERVER_DIR, filename);
if (!await fs.pathExists(filePath)) {
return res.status(404).json({ error: 'Fichier non trouvé' });
}
const content = await fs.readFile(filePath, 'utf-8');
const fileLines = content.split('\n');
const lastLines = fileLines.slice(-parseInt(lines));
res.json({
logs: lastLines,
file: filename,
totalLines: fileLines.length
});
} catch (error) {
console.error('Erreur lecture log:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.get('/search', isAuthenticated, async (req, res) => {
try {
const { query } = req.query;
if (!query || query.length < 2) {
return res.status(400).json({ error: 'Requête trop courte' });
}
let logFile = path.join(SERVER_DIR, 'latest.log');
if (!await fs.pathExists(logFile)) {
logFile = path.join(SERVER_DIR, 'ForgeModLoader-server-0.log');
}
if (!await fs.pathExists(logFile)) {
return res.json({ results: [] });
}
const content = await fs.readFile(logFile, 'utf-8');
const lines = content.split('\n');
const results = lines
.map((line, index) => ({ line, index }))
.filter(({ line }) => line.toLowerCase().includes(query.toLowerCase()))
.slice(-50);
res.json({ results });
} catch (error) {
console.error('Erreur recherche:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

View File

@@ -0,0 +1,62 @@
const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
router.get('/', isAuthenticated, async (req, res) => {
try {
const playersDir = path.join(SERVER_DIR, 'playerdata');
const statsDir = path.join(SERVER_DIR, 'stats');
if (!await fs.pathExists(playersDir)) {
return res.json({ players: [] });
}
const files = await fs.readdir(playersDir);
const uuidRegex = /^[0-9a-f-]{36}\.dat$/i;
const playerFiles = files.filter(f => uuidRegex.test(f));
// Récupérer les noms des joueurs
const usercacheFile = path.join(SERVER_DIR, 'usercache.json');
let usercache = {};
if (await fs.pathExists(usercacheFile)) {
try {
const cache = await fs.readJson(usercacheFile);
usercache = cache.reduce((acc, entry) => {
acc[entry.uuid] = entry.name;
return acc;
}, {});
} catch (e) {
console.error('Erreur lecture usercache:', e);
}
}
const players = playerFiles.map(file => {
const uuid = file.replace('.dat', '');
return {
uuid,
name: usercache[uuid] || 'Inconnu',
lastPlayed: new Date()
};
});
res.json({ players });
} catch (error) {
console.error('Erreur joueurs:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

183
backend/src/routes/rcon.js Normal file
View File

@@ -0,0 +1,183 @@
const express = require('express');
const RconClient = require('../utils/rcon');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
async function getRconConfig() {
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
try {
const content = await fs.readFile(serverPropsFile, 'utf-8');
const lines = content.split('\n');
let config = { host: 'localhost', port: 25575, password: '' };
for (let line of lines) {
if (line.startsWith('rcon.port=')) {
config.port = parseInt(line.split('=')[1].trim());
} else if (line.startsWith('rcon.password=')) {
config.password = line.split('=')[1].trim();
}
}
return config;
} catch (e) {
console.error('Erreur lecture config RCON:', e);
return null;
}
}
async function addToHistory(command, response) {
try {
const historyFile = path.join(SERVER_DIR, '.web-admin/rcon-history.json');
await fs.ensureDir(path.dirname(historyFile));
let history = [];
if (await fs.pathExists(historyFile)) {
history = await fs.readJson(historyFile);
}
history.push({
timestamp: new Date(),
command,
response: response.substring(0, 500)
});
await fs.writeJson(historyFile, history.slice(-100), { spaces: 2 });
} catch (e) {
console.error('Erreur sauvegarde historique:', e);
}
}
router.post('/command', isAuthenticated, async (req, res) => {
try {
const { command } = req.body;
if (!command) {
return res.status(400).json({ error: 'Commande manquante' });
}
const config = await getRconConfig();
if (!config || !config.password) {
return res.status(500).json({ error: 'Configuration RCON non trouvée' });
}
const rcon = new RconClient(config.host, config.port, config.password);
try {
await rcon.connect();
const response = await rcon.sendCommand(command);
rcon.disconnect();
await addToHistory(command, response);
res.json({ response, command });
} catch (e) {
console.error('Erreur RCON:', e);
rcon.disconnect();
res.status(500).json({ error: `Erreur RCON: ${e.message}` });
}
} catch (error) {
console.error('Erreur commande:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.get('/history', isAuthenticated, async (req, res) => {
try {
const historyFile = path.join(SERVER_DIR, '.web-admin/rcon-history.json');
if (await fs.pathExists(historyFile)) {
const history = await fs.readJson(historyFile);
res.json(history);
} else {
res.json([]);
}
} catch (e) {
console.error('Erreur historique:', e);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/restart', isAuthenticated, async (req, res) => {
try {
const config = await getRconConfig();
if (!config || !config.password) {
return res.status(500).json({ error: 'Configuration RCON non trouvée' });
}
const rcon = new RconClient(config.host, config.port, config.password);
try {
await rcon.connect();
await rcon.sendCommand('say §6[ADMIN] Serveur redémarrage dans 30 secondes!');
await new Promise(resolve => setTimeout(resolve, 5000));
await rcon.sendCommand('say §6[ADMIN] 25 secondes...');
await new Promise(resolve => setTimeout(resolve, 5000));
await rcon.sendCommand('say §6[ADMIN] 20 secondes...');
await new Promise(resolve => setTimeout(resolve, 10000));
await rcon.sendCommand('say §6[ADMIN] Redémarrage maintenant!');
const response = await rcon.sendCommand('stop');
rcon.disconnect();
await addToHistory('stop', response);
res.json({ message: 'Redémarrage en cours...' });
} catch (e) {
console.error('Erreur redémarrage:', e);
rcon.disconnect();
res.status(500).json({ error: `Erreur: ${e.message}` });
}
} catch (error) {
console.error('Erreur:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/save', isAuthenticated, async (req, res) => {
try {
const config = await getRconConfig();
if (!config || !config.password) {
return res.status(500).json({ error: 'Configuration RCON non trouvée' });
}
const rcon = new RconClient(config.host, config.port, config.password);
try {
await rcon.connect();
await rcon.sendCommand('say §6[ADMIN] Sauvegarde en cours...');
const r1 = await rcon.sendCommand('save-off');
const r2 = await rcon.sendCommand('save-all');
const r3 = await rcon.sendCommand('save-on');
await rcon.sendCommand('say §6[ADMIN] Sauvegarde terminée!');
rcon.disconnect();
await addToHistory('save-all', r2);
res.json({ message: 'Sauvegarde effectuée' });
} catch (e) {
console.error('Erreur sauvegarde:', e);
rcon.disconnect();
res.status(500).json({ error: `Erreur: ${e.message}` });
}
} catch (error) {
console.error('Erreur:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

View File

@@ -0,0 +1,79 @@
const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
router.get('/', isAuthenticated, async (req, res) => {
try {
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
if (!await fs.pathExists(serverPropsFile)) {
return res.status(404).json({ error: 'server.properties non trouvé' });
}
const content = await fs.readFile(serverPropsFile, 'utf-8');
const lines = content.split('\n');
const properties = {};
lines.forEach(line => {
if (line && !line.startsWith('#')) {
const [key, ...valueParts] = line.split('=');
properties[key.trim()] = valueParts.join('=').trim();
}
});
res.json({ properties });
} catch (error) {
console.error('Erreur serveur:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/update', isAuthenticated, async (req, res) => {
try {
const { property, value } = req.body;
if (!property || value === undefined) {
return res.status(400).json({ error: 'Données manquantes' });
}
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
let content = await fs.readFile(serverPropsFile, 'utf-8');
const lines = content.split('\n');
let found = false;
const newContent = lines.map(line => {
if (line.startsWith(property + '=')) {
found = true;
return `${property}=${value}`;
}
return line;
}).join('\n');
if (!found) {
content = newContent + '\n' + property + '=' + value;
} else {
content = newContent;
}
await fs.writeFile(serverPropsFile, content, 'utf-8');
res.json({ message: 'Propriété mise à jour' });
} catch (error) {
console.error('Erreur mise à jour:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

View File

@@ -0,0 +1,123 @@
const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const { SERVER_DIR } = require('../server');
const router = express.Router();
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Non authentifié' });
}
}
async function getWhitelistFile() {
const jsonFile = path.join(SERVER_DIR, 'whitelist.json');
const txtFile = path.join(SERVER_DIR, 'whitelist.txt');
if (await fs.pathExists(jsonFile)) {
return jsonFile;
} else if (await fs.pathExists(txtFile)) {
return txtFile;
}
return jsonFile;
}
router.get('/', isAuthenticated, async (req, res) => {
try {
const whitelistFile = await getWhitelistFile();
if (!await fs.pathExists(whitelistFile)) {
return res.json({ whitelist: [], format: 'json' });
}
if (whitelistFile.endsWith('.json')) {
const whitelist = await fs.readJson(whitelistFile);
res.json({ whitelist, format: 'json' });
} else {
const content = await fs.readFile(whitelistFile, 'utf-8');
const whitelist = content.split('\n').filter(line => line.trim());
res.json({ whitelist, format: 'txt' });
}
} catch (error) {
console.error('Erreur whitelist:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/add', isAuthenticated, async (req, res) => {
try {
const { username } = req.body;
if (!username) {
return res.status(400).json({ error: 'Nom manquant' });
}
const whitelistFile = await getWhitelistFile();
if (whitelistFile.endsWith('.json')) {
let whitelist = [];
if (await fs.pathExists(whitelistFile)) {
whitelist = await fs.readJson(whitelistFile);
}
if (whitelist.some(w => (typeof w === 'string' ? w : w.name) === username)) {
return res.status(400).json({ error: 'Joueur déjà en whitelist' });
}
whitelist.push({ name: username, uuid: '' });
await fs.writeJson(whitelistFile, whitelist, { spaces: 2 });
} else {
let content = '';
if (await fs.pathExists(whitelistFile)) {
content = await fs.readFile(whitelistFile, 'utf-8');
}
if (content.includes(username)) {
return res.status(400).json({ error: 'Joueur déjà en whitelist' });
}
content += (content ? '\n' : '') + username;
await fs.writeFile(whitelistFile, content, 'utf-8');
}
res.json({ message: 'Joueur ajouté à la whitelist' });
} catch (error) {
console.error('Erreur ajout whitelist:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
router.post('/remove', isAuthenticated, async (req, res) => {
try {
const { username } = req.body;
if (!username) {
return res.status(400).json({ error: 'Nom manquant' });
}
const whitelistFile = await getWhitelistFile();
if (whitelistFile.endsWith('.json')) {
let whitelist = await fs.readJson(whitelistFile);
whitelist = whitelist.filter(w => (typeof w === 'string' ? w : w.name) !== username);
await fs.writeJson(whitelistFile, whitelist, { spaces: 2 });
} else {
let content = await fs.readFile(whitelistFile, 'utf-8');
content = content.split('\n').filter(line => line.trim() !== username).join('\n');
await fs.writeFile(whitelistFile, content, 'utf-8');
}
res.json({ message: 'Joueur retiré de la whitelist' });
} catch (error) {
console.error('Erreur suppression whitelist:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
module.exports = router;

73
backend/src/server.js Normal file
View File

@@ -0,0 +1,73 @@
const express = require('express');
const session = require('express-session');
const path = require('path');
const dotenv = require('dotenv');
const cors = require('cors');
const fs = require('fs-extra');
dotenv.config();
const app = express();
const PORT = process.env.PORT || 4001;
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
// Middlewares
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors({
origin: process.env.NODE_ENV === 'production' ? false : true,
credentials: true
}));
// Session config
app.use(session({
secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-prod',
resave: false,
saveUninitialized: true,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 heures
}
}));
// Importation des routes
const authRoutes = require('./routes/auth');
const serverRoutes = require('./routes/server');
const rconRoutes = require('./routes/rcon');
const logsRoutes = require('./routes/logs');
const playersRoutes = require('./routes/players');
const whitelistRoutes = require('./routes/whitelist');
const backupRoutes = require('./routes/backup');
// Routes API
app.use('/api/auth', authRoutes);
app.use('/api/server', serverRoutes);
app.use('/api/rcon', rconRoutes);
app.use('/api/logs', logsRoutes);
app.use('/api/players', playersRoutes);
app.use('/api/whitelist', whitelistRoutes);
app.use('/api/backup', backupRoutes);
// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date() });
});
// Erreur 404
app.use((req, res) => {
res.status(404).json({ error: 'Route non trouvée' });
});
// Error handler
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Erreur serveur interne' });
});
app.listen(PORT, () => {
console.log(`\n🚀 Backend Admin NationsGlory démarré sur http://localhost:${PORT}`);
console.log(`📁 Répertoire du serveur: ${SERVER_DIR}\n`);
});
module.exports = { app, SERVER_DIR };

141
backend/src/utils/rcon.js Normal file
View File

@@ -0,0 +1,141 @@
const net = require('net');
class RconClient {
constructor(host, port, password) {
this.host = host;
this.port = port;
this.password = password;
this.socket = null;
this.authenticated = false;
this.packetId = 0;
this.responseBuffer = Buffer.alloc(0);
}
createPacket(type, payload) {
const payloadBuffer = Buffer.from(payload, 'utf8');
const body = Buffer.alloc(4 + payloadBuffer.length + 1);
body.writeInt32BE(type, 0);
payloadBuffer.copy(body, 4);
body[4 + payloadBuffer.length] = 0;
const size = body.length;
const packet = Buffer.alloc(4 + size);
packet.writeInt32LE(size, 0);
body.copy(packet, 4);
return packet;
}
parsePacket(buffer) {
if (buffer.length < 12) return null;
const size = buffer.readInt32LE(0);
if (buffer.length < 4 + size) return null;
const id = buffer.readInt32LE(4);
const type = buffer.readInt32LE(8);
let payload = '';
if (size > 8) {
payload = buffer.slice(12, 4 + size - 1).toString('utf8');
}
return { id, type, payload, totalSize: 4 + size };
}
async connect() {
return new Promise((resolve, reject) => {
this.socket = net.createConnection({
host: this.host,
port: this.port
});
this.socket.on('connect', () => {
console.log('✓ Connecté au serveur RCON');
this.authenticate().then(resolve).catch(reject);
});
this.socket.on('error', reject);
setTimeout(() => {
reject(new Error('Timeout de connexion'));
}, 5000);
});
}
async authenticate() {
return new Promise((resolve, reject) => {
this.packetId++;
const packet = this.createPacket(3, this.password);
this.socket.write(packet);
const handleData = (data) => {
this.responseBuffer = Buffer.concat([this.responseBuffer, data]);
const response = this.parsePacket(this.responseBuffer);
if (response && response.id === this.packetId) {
this.responseBuffer = this.responseBuffer.slice(response.totalSize);
this.socket.removeListener('data', handleData);
if (response.id !== -1) {
this.authenticated = true;
console.log('✓ Authentifié RCON');
resolve();
} else {
reject(new Error('Mot de passe RCON incorrect'));
}
}
};
this.socket.on('data', handleData);
setTimeout(() => {
reject(new Error('Timeout d\'authentification'));
}, 5000);
});
}
async sendCommand(command) {
return new Promise((resolve, reject) => {
if (!this.authenticated) {
reject(new Error('Non authentifié'));
return;
}
this.packetId++;
const id = this.packetId;
const packet = this.createPacket(2, command);
this.socket.write(packet);
const handleData = (data) => {
this.responseBuffer = Buffer.concat([this.responseBuffer, data]);
const response = this.parsePacket(this.responseBuffer);
if (response && response.id === id) {
this.responseBuffer = this.responseBuffer.slice(response.totalSize);
this.socket.removeListener('data', handleData);
resolve(response.payload);
}
};
this.socket.on('data', handleData);
setTimeout(() => {
reject(new Error('Timeout de commande'));
}, 10000);
});
}
disconnect() {
if (this.socket) {
this.socket.destroy();
this.socket = null;
this.authenticated = false;
}
}
}
module.exports = RconClient;

20
docker-compose.yml Normal file
View File

@@ -0,0 +1,20 @@
version: "3.8"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "4001:4001"
environment:
NODE_ENV: production
PORT: 4001
SERVER_DIR: /home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
RCON_HOST: host.docker.internal # ou votre adresse IP
RCON_PORT: 25575
SESSION_SECRET: change-this-in-production
volumes:
- /home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red:/mc-server:ro
restart: unless-stopped
network_mode: host

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);
}
}

41
install.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Configuration pour l'interface web du serveur Minecraft NationsGlory
# ====================================================================
echo "🚀 Installation de l'interface web NationsGlory Admin..."
# Vérifier que Node.js est installé
if ! command -v node &> /dev/null; then
echo "❌ Node.js n'est pas installé. Veuillez l'installer d'abord."
exit 1
fi
echo "✓ Node.js détecté: $(node --version)"
# Créer le fichier .env
if [ ! -f backend/.env ]; then
echo "Création du fichier .env..."
cp backend/.env.example backend/.env
echo "✓ Fichier .env créé. À personnaliser dans backend/.env"
fi
# Installer les dépendances du backend
echo ""
echo "📦 Installation des dépendances backend..."
cd backend
npm install
cd ..
echo ""
echo "✅ Installation terminée!"
echo ""
echo "📝 Prochaines étapes:"
echo "1. Modifiez backend/.env avec la bonne configuration"
echo "2. Lancez le serveur: cd backend && npm start"
echo "3. Accédez à l'interface: http://localhost:3000"
echo ""
echo " IMPORTANT: Activez RCON dans le server.properties du serveur MC:"
echo " - enable-rcon=true"
echo " - rcon.port=25575"
echo " - rcon.password=votre_mdp"

21
setup.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# Rendre les scripts exécutables
chmod +x install.sh
chmod +x start.sh
# Créer les dossiers de data s'ils n'existent pas
mkdir -p backend/data
echo "✅ Préparation complète!"
echo ""
echo "📖 Documentation:"
echo " - README.md - Vue d'ensemble"
echo " - CONFIGURATION.md - Guide de configuration détaillé"
echo " - DEPLOYMENT.md - Déploiement en production"
echo ""
echo "🚀 Pour démarrer:"
echo " 1. ./install.sh"
echo " 2. Éditer backend/.env"
echo " 3. ./start.sh"
echo ""

39
start.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
# Script de démarrage pour le serveur et l'interface web
# =========================================================
# Couleurs
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}🎮 NationsGlory Admin Panel - Démarrage${NC}"
echo ""
# Vérifier que le backend existe
if [ ! -d "backend" ]; then
echo -e "${RED}❌ Dossier backend non trouvé!${NC}"
exit 1
fi
# Vérifier le fichier .env
if [ ! -f "backend/.env" ]; then
echo -e "${RED}❌ Fichier backend/.env non trouvé!${NC}"
echo -e "${YELLOW}Copiez d'abord: cp backend/.env.example backend/.env${NC}"
exit 1
fi
# Charger les variables d'environnement
export $(cat backend/.env | grep -v '^#' | xargs)
echo -e "${GREEN}✓ Variables d'environnement chargées${NC}"
echo " - Port: $PORT"
echo " - Serveur MC: $SERVER_DIR"
echo ""
# Démarrer le backend
echo -e "${YELLOW}⏳ Démarrage du backend...${NC}"
cd backend
npm start