From abb51904d73a647575b3225423fa39710b2cebbf Mon Sep 17 00:00:00 2001 From: "y.campiontrebouta@innotexnas.ovh" Date: Wed, 4 Feb 2026 19:04:46 +0100 Subject: [PATCH] initial commit --- .gitignore | 15 + CONFIGURATION.md | 318 ++++++++++++ DEPLOYMENT.md | 64 +++ Dockerfile | 14 + INDEX.md | 244 +++++++++ MAINTENANCE.md | 347 +++++++++++++ QUICKSTART.md | 136 +++++ README.md | 228 ++++++++ backend/.env.example | 6 + backend/package.json | 25 + backend/src/routes/auth.js | 200 +++++++ backend/src/routes/backup.js | 99 ++++ backend/src/routes/logs.js | 138 +++++ backend/src/routes/players.js | 62 +++ backend/src/routes/rcon.js | 183 +++++++ backend/src/routes/server.js | 79 +++ backend/src/routes/whitelist.js | 123 +++++ backend/src/server.js | 73 +++ backend/src/utils/rcon.js | 141 +++++ docker-compose.yml | 20 + frontend/package.json | 9 + frontend/public/css/style.css | 484 +++++++++++++++++ frontend/public/index.html | 13 + frontend/public/js/app.js | 889 ++++++++++++++++++++++++++++++++ install.sh | 41 ++ setup.sh | 21 + start.sh | 39 ++ 27 files changed, 4011 insertions(+) create mode 100644 .gitignore create mode 100644 CONFIGURATION.md create mode 100644 DEPLOYMENT.md create mode 100644 Dockerfile create mode 100644 INDEX.md create mode 100644 MAINTENANCE.md create mode 100644 QUICKSTART.md create mode 100644 README.md create mode 100644 backend/.env.example create mode 100644 backend/package.json create mode 100644 backend/src/routes/auth.js create mode 100644 backend/src/routes/backup.js create mode 100644 backend/src/routes/logs.js create mode 100644 backend/src/routes/players.js create mode 100644 backend/src/routes/rcon.js create mode 100644 backend/src/routes/server.js create mode 100644 backend/src/routes/whitelist.js create mode 100644 backend/src/server.js create mode 100644 backend/src/utils/rcon.js create mode 100644 docker-compose.yml create mode 100644 frontend/package.json create mode 100644 frontend/public/css/style.css create mode 100644 frontend/public/index.html create mode 100644 frontend/public/js/app.js create mode 100755 install.sh create mode 100755 setup.sh create mode 100755 start.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2584c20 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +node_modules/ +.env +.env.local +*.log +*.log.* +dist/ +build/ +.DS_Store +.idea/ +.vscode/ +*.swp +*.swo +*~ +backups/ +.web-admin/ diff --git a/CONFIGURATION.md b/CONFIGURATION.md new file mode 100644 index 0000000..74dca06 --- /dev/null +++ b/CONFIGURATION.md @@ -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! 🎼 diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..ec8b539 --- /dev/null +++ b/DEPLOYMENT.md @@ -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 +# ============================ + +# +# ServerName admin.nationglory.com +# Redirect permanent / https://admin.nationglory.com/ +# +# +# +# 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" +# + +# Environment Variables +# ====================== +# Set these in production: +# NODE_ENV=production +# SESSION_SECRET=use-a-strong-random-key-here +# PORT=3000 (internal, proxy on 80/443) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..74eeed5 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/INDEX.md b/INDEX.md new file mode 100644 index 0000000..bf68d8f --- /dev/null +++ b/INDEX.md @@ -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) diff --git a/MAINTENANCE.md b/MAINTENANCE.md new file mode 100644 index 0000000..3da2849 --- /dev/null +++ b/MAINTENANCE.md @@ -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= + +# 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 # 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! diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..c00005b --- /dev/null +++ b/QUICKSTART.md @@ -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! diff --git a/README.md b/README.md new file mode 100644 index 0000000..305c9e5 --- /dev/null +++ b/README.md @@ -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 diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..2dacbf2 --- /dev/null +++ b/backend/.env.example @@ -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 diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..c7881f7 --- /dev/null +++ b/backend/package.json @@ -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" + } +} diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js new file mode 100644 index 0000000..c8f87ac --- /dev/null +++ b/backend/src/routes/auth.js @@ -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; diff --git a/backend/src/routes/backup.js b/backend/src/routes/backup.js new file mode 100644 index 0000000..0be4d1b --- /dev/null +++ b/backend/src/routes/backup.js @@ -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; diff --git a/backend/src/routes/logs.js b/backend/src/routes/logs.js new file mode 100644 index 0000000..47a8eb3 --- /dev/null +++ b/backend/src/routes/logs.js @@ -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; diff --git a/backend/src/routes/players.js b/backend/src/routes/players.js new file mode 100644 index 0000000..b3d6552 --- /dev/null +++ b/backend/src/routes/players.js @@ -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; diff --git a/backend/src/routes/rcon.js b/backend/src/routes/rcon.js new file mode 100644 index 0000000..8520541 --- /dev/null +++ b/backend/src/routes/rcon.js @@ -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; diff --git a/backend/src/routes/server.js b/backend/src/routes/server.js new file mode 100644 index 0000000..b230e8b --- /dev/null +++ b/backend/src/routes/server.js @@ -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; diff --git a/backend/src/routes/whitelist.js b/backend/src/routes/whitelist.js new file mode 100644 index 0000000..37b2147 --- /dev/null +++ b/backend/src/routes/whitelist.js @@ -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; diff --git a/backend/src/server.js b/backend/src/server.js new file mode 100644 index 0000000..5ffd783 --- /dev/null +++ b/backend/src/server.js @@ -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 }; diff --git a/backend/src/utils/rcon.js b/backend/src/utils/rcon.js new file mode 100644 index 0000000..1176c08 --- /dev/null +++ b/backend/src/utils/rcon.js @@ -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; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7db6ec5 --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..5ac3525 --- /dev/null +++ b/frontend/package.json @@ -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" + } +} diff --git a/frontend/public/css/style.css b/frontend/public/css/style.css new file mode 100644 index 0000000..9fd4584 --- /dev/null +++ b/frontend/public/css/style.css @@ -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); +} diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 0000000..ebbc6f7 --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,13 @@ + + + + + + NationsGlory Admin Panel + + + +
+ + + diff --git a/frontend/public/js/app.js b/frontend/public/js/app.js new file mode 100644 index 0000000..4bc0b9a --- /dev/null +++ b/frontend/public/js/app.js @@ -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 = ` + + `; + + // 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 = ` +
+
+
+

🎼 NationsGlory Admin Panel

+
+ +
+ +
+ + + + +
+ ${getDashboardHTML()} +
+ + +
+ ${getConsoleHTML()} +
+ + +
+ ${getLogsHTML()} +
+ + +
+ ${getPlayersHTML()} +
+ + +
+ ${getWhitelistHTML()} +
+ + +
+ ${getBackupsHTML()} +
+ + +
+ ${getSettingsHTML()} +
+
+ `; + + // 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 ` +
+

🎼 Contrîle du Serveur

+
+ + + +
+
+ +
+
+

État Serveur

+

En ligne

+
+
+

Joueurs Connectés

+

--

+
+
+

Version Forge

+

1.6.4

+
+
+ +
+

📊 Informations Rapides

+ + + + +
Chargement...
+
+ `; +} + +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 = ` + Nom Serveur${data.properties['motd'] || 'NationsGlory'} + Mode Jeu${data.properties['gamemode'] || 'Survival'} + Difficulté${data.properties['difficulty'] || '2'} + PvP${data.properties['pvp'] === 'true' ? 'Activé' : 'Désactivé'} + Port${data.properties['server-port'] || '25565'} + Max Joueurs${data.properties['max-players'] || '20'} + `; + } + } catch (e) { + console.error('Erreur chargement dashboard:', e); + } +} + +// ========== CONSOLE RCON ========== +function getConsoleHTML() { + return ` +
+

⌚ Console RCON

+

Exécutez des commandes directement sur le serveur

+
+
+ + +
+ +

Historique

+
+
+ `; +} + +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 => ` +
+ ${h.command}
+ ${new Date(h.timestamp).toLocaleTimeString()} +
+ `).join('') || '

Aucun historique

'; + } 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 += `
$ ${command}
`; + 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 += `
${data.response}
`; + showMessage('Commande exécutée', 'success', 'dashboardMessage'); + } else { + output.innerHTML += `
Erreur: ${data.error}
`; + showMessage(data.error, 'error', 'dashboardMessage'); + } + + output.scrollTop = output.scrollHeight; + } catch (e) { + console.error('Erreur envoi commande:', e); + output.innerHTML += `
Erreur serveur
`; + showMessage('Erreur serveur', 'error', 'dashboardMessage'); + } +} + +// ========== LOGS ========== +function getLogsHTML() { + return ` +
+

📜 Logs

+
+ + +
+
+
+ `; +} + +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 `
${escapeHtml(line)}
`; + }).join(''); + + container.scrollTop = container.scrollHeight; + } catch (e) { + console.error('Erreur chargement logs:', e); + document.getElementById('logsContainer').innerHTML = '
Erreur chargement
'; + } +} + +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) => ` +
+ [${r.index}] ${escapeHtml(r.line)} +
+ `).join(''); + } catch (e) { + console.error('Erreur recherche:', e); + } +} + +// ========== JOUEURS ========== +function getPlayersHTML() { + return ` +
+

đŸ‘„ Joueurs ConnectĂ©s

+

Liste des joueurs qui se sont connectés

+ + + + + + + + + + + +
NomUUIDDerniĂšre Connexion
Chargement...
+
+ `; +} + +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 => ` + + ${p.name} + ${p.uuid} + ${new Date(p.lastPlayed).toLocaleString()} + + `).join(''); + } else { + table.innerHTML = 'Aucun joueur'; + } + } catch (e) { + console.error('Erreur joueurs:', e); + document.getElementById('playersTable').innerHTML = 'Erreur'; + } +} + +// ========== WHITELIST ========== +function getWhitelistHTML() { + return ` +
+

✅ Whitelist

+
+ + +
+ + + + + + + + + + +
JoueurActions
Chargement...
+
+ `; +} + +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 => ` + + ${p} + + + + + `).join(''); + } else { + table.innerHTML = 'Whitelist vide'; + } + } catch (e) { + console.error('Erreur whitelist:', e); + document.getElementById('whitelistTable').innerHTML = 'Erreur'; + } +} + +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 ` +
+

đŸ’Ÿ Backups

+
+ + +
+ + + + + + + + + + + + +
NomTailleDateActions
Chargement...
+
+ `; +} + +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 => ` + + ${b.name} + ${b.size} + ${new Date(b.created).toLocaleString()} + + + + + `).join(''); + } else { + table.innerHTML = 'Aucun backup'; + } + } catch (e) { + console.error('Erreur backups:', e); + document.getElementById('backupsTable').innerHTML = 'Erreur'; + } +} + +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 ` +
+

⚙ ParamĂštres du Serveur

+ + + + + + + + + + +
ParamĂštreValeur
Chargement...
+
+ `; +} + +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 => ` + + ${k} + ${escapeHtml(data.properties[k])} + + `).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 = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + 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); + } +} diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..89fd978 --- /dev/null +++ b/install.sh @@ -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" diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..8446792 --- /dev/null +++ b/setup.sh @@ -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 "" diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..1714960 --- /dev/null +++ b/start.sh @@ -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