commit abb51904d73a647575b3225423fa39710b2cebbf Author: y.campiontrebouta@innotexnas.ovh Date: Wed Feb 4 19:04:46 2026 +0100 initial commit 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