initial commit
This commit is contained in:
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
node_modules/
|
||||
.env
|
||||
.env.local
|
||||
*.log
|
||||
*.log.*
|
||||
dist/
|
||||
build/
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
backups/
|
||||
.web-admin/
|
||||
318
CONFIGURATION.md
Normal file
318
CONFIGURATION.md
Normal file
@@ -0,0 +1,318 @@
|
||||
# 🎮 Guide Complet - Interface Web NationsGlory Admin
|
||||
|
||||
## Étape 1: Préparation du Serveur Minecraft
|
||||
|
||||
### 1.1 Éditer server.properties
|
||||
|
||||
Localisez le fichier `server.properties` du serveur MC et assurez-vous que RCON est activé:
|
||||
|
||||
```properties
|
||||
# Ligne 1: Activer RCON
|
||||
enable-rcon=true
|
||||
|
||||
# Ligne 2: Port RCON (25575 par défaut)
|
||||
rcon.port=25575
|
||||
|
||||
# Ligne 3: Mot de passe RCON (important!)
|
||||
rcon.password=YourStrongPassword123
|
||||
|
||||
# Autres paramètres importants
|
||||
motd=NationsGlory Server
|
||||
gamemode=survival
|
||||
difficulty=3
|
||||
pvp=true
|
||||
max-players=20
|
||||
```
|
||||
|
||||
### 1.2 Redémarrer le Serveur MC
|
||||
|
||||
Redémarrez le serveur pour appliquer les changements RCON:
|
||||
```bash
|
||||
# Si le serveur tourne avec un script
|
||||
./stop.sh
|
||||
./start.sh
|
||||
|
||||
# Attendez qu'il redémarre complètement
|
||||
```
|
||||
|
||||
Vérifiez que RCON est actif dans les logs du serveur (vous devriez voir quelque chose comme):
|
||||
```
|
||||
[Server thread/INFO]: RCON running on 0.0.0.0:25575
|
||||
```
|
||||
|
||||
## Étape 2: Installation de l'Interface Web
|
||||
|
||||
### 2.1 Vérifier les Prérequis
|
||||
|
||||
```bash
|
||||
# Vérifier Node.js
|
||||
node --version # Doit être v14 ou plus
|
||||
|
||||
# Vérifier npm
|
||||
npm --version # Doit être v6 ou plus
|
||||
```
|
||||
|
||||
Si Node.js n'est pas installé:
|
||||
- **Linux**: `sudo apt-get install nodejs npm`
|
||||
- **macOS**: `brew install node`
|
||||
- **Windows**: Télécharger depuis https://nodejs.org
|
||||
|
||||
### 2.2 Installation Automatique
|
||||
|
||||
```bash
|
||||
cd /home/innotex/Documents/Projet/Serveur\ NationsGlory/WebNationsGlory_ServeurBuild_Red
|
||||
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
Cela va:
|
||||
- ✓ Vérifier Node.js
|
||||
- ✓ Créer le fichier .env
|
||||
- ✓ Installer les dépendances npm
|
||||
|
||||
## Étape 3: Configuration
|
||||
|
||||
### 3.1 Éditer le fichier .env
|
||||
|
||||
Ouvrez `backend/.env` avec votre éditeur préféré:
|
||||
|
||||
```env
|
||||
# Mode d'exécution
|
||||
NODE_ENV=development
|
||||
|
||||
# Port de l'application (8080, 3000, etc.)
|
||||
PORT=3000
|
||||
|
||||
# Clé de session (générer une clé aléatoire forte)
|
||||
SESSION_SECRET=abc123def456ghi789jkl012mno345pqr
|
||||
|
||||
# Chemin EXACT vers le dossier du serveur MC
|
||||
SERVER_DIR=/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
|
||||
|
||||
# Paramètres RCON
|
||||
RCON_HOST=localhost
|
||||
RCON_PORT=25575
|
||||
```
|
||||
|
||||
⚠️ **Important**:
|
||||
- Vérifiez que `SERVER_DIR` est le **chemin exact**
|
||||
- Le RCON_HOST peut être `localhost`, `127.0.0.1`, ou l'IP du serveur
|
||||
- SESSION_SECRET doit être unique (utilisez une clé forte)
|
||||
|
||||
## Étape 4: Lancement
|
||||
|
||||
### 4.1 Démarrer l'Application
|
||||
|
||||
```bash
|
||||
cd /home/innotex/Documents/Projet/Serveur\ NationsGlory/WebNationsGlory_ServeurBuild_Red
|
||||
|
||||
chmod +x start.sh
|
||||
./start.sh
|
||||
```
|
||||
|
||||
Vous devriez voir:
|
||||
```
|
||||
🚀 Backend Admin NationsGlory démarré sur http://localhost:3000
|
||||
📁 Répertoire du serveur: /home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
|
||||
```
|
||||
|
||||
### 4.2 Accéder à l'Interface
|
||||
|
||||
Ouvrez votre navigateur et allez à:
|
||||
```
|
||||
http://localhost:3000
|
||||
```
|
||||
|
||||
## Étape 5: Premier Démarrage
|
||||
|
||||
### 5.1 Créer le Compte Admin
|
||||
|
||||
La première fois que vous accédez à l'interface:
|
||||
|
||||
1. **Tab "Enregistrement"** s'affiche
|
||||
2. Remplissez:
|
||||
- **Nom d'utilisateur**: Le nom que vous voulez pour vous connecter (ex: `admin`)
|
||||
- **Mot de passe**: Un mot de passe fort
|
||||
- **Pseudo Minecraft**: DOIT être un OP du serveur (ex: `VotreNomMC`)
|
||||
|
||||
3. Cliquez sur "Créer le compte"
|
||||
|
||||
⚠️ **Attention**: Le pseudo Minecraft doit être dans le fichier `ops.txt` ou `ops.json` du serveur!
|
||||
|
||||
### 5.2 Se Connecter
|
||||
|
||||
Après l'enregistrement:
|
||||
1. Remplissez le formulaire de connexion
|
||||
2. Entrez le nom d'utilisateur et mot de passe
|
||||
3. Cliquez "Se connecter"
|
||||
|
||||
### 5.3 Premier Test
|
||||
|
||||
Une fois connecté:
|
||||
1. Allez à l'onglet **Console RCON**
|
||||
2. Entrez une commande simple: `/time query daytime`
|
||||
3. Cliquez "Envoyer"
|
||||
4. Vous devriez voir la réponse
|
||||
|
||||
Si ça marche, RCON est correctement configuré! ✓
|
||||
|
||||
## 🆘 Dépannage
|
||||
|
||||
### Problème: "Erreur RCON: Timeout"
|
||||
|
||||
**Cause**: Le serveur MC ne répond pas
|
||||
|
||||
**Solutions**:
|
||||
1. Vérifiez que le serveur MC est en ligne
|
||||
2. Vérifiez que RCON est activé dans server.properties
|
||||
3. Vérifiez le port RCON (25575 par défaut)
|
||||
4. Testez RCON avec un autre client:
|
||||
```bash
|
||||
telnet localhost 25575
|
||||
```
|
||||
|
||||
### Problème: "Mot de passe RCON incorrect"
|
||||
|
||||
**Cause**: Le mot de passe ne correspond pas
|
||||
|
||||
**Solution**:
|
||||
1. Vérifiez le mot de passe dans `server.properties`
|
||||
2. Modifiez via l'interface: Dashboard → Changer RCON
|
||||
3. Redémarrez le serveur MC après changement
|
||||
|
||||
### Problème: "Le joueur n'est pas OP sur le serveur"
|
||||
|
||||
**Cause**: Votre pseudo MC n'est pas OP
|
||||
|
||||
**Solution**:
|
||||
1. Sur le serveur MC, dans la console:
|
||||
```
|
||||
op VotreNomMC
|
||||
```
|
||||
2. Attendez que le serveur se redémarre ou recharge les OPs
|
||||
3. Réessayez l'enregistrement
|
||||
|
||||
### Problème: "Impossible de se connecter à localhost:3000"
|
||||
|
||||
**Cause**: L'application n'est pas lancée ou sur le mauvais port
|
||||
|
||||
**Solution**:
|
||||
1. Vérifiez que `npm start` est lancé dans `backend/`
|
||||
2. Vérifiez le PORT dans `.env`
|
||||
3. Assurez-vous qu'aucune autre application n'utilise ce port:
|
||||
```bash
|
||||
lsof -i :3000 # Linux/Mac
|
||||
netstat -ano | findstr :3000 # Windows
|
||||
```
|
||||
|
||||
### Problème: "Node.js n'est pas trouvé"
|
||||
|
||||
**Solution**: Installer Node.js
|
||||
- **Ubuntu/Debian**: `sudo apt-get install nodejs npm`
|
||||
- **CentOS/RHEL**: `sudo yum install nodejs npm`
|
||||
- **macOS**: `brew install node`
|
||||
- **Windows**: https://nodejs.org
|
||||
|
||||
## 📊 Fonctionnalités Détaillées
|
||||
|
||||
### Console RCON
|
||||
Exécutez n'importe quelle commande Minecraft:
|
||||
```
|
||||
/say Bienvenue!
|
||||
/gamemode 1 @p
|
||||
/give @p diamond 64
|
||||
/weather clear
|
||||
```
|
||||
|
||||
### Gestion des Logs
|
||||
- Affichage temps réel
|
||||
- Recherche par mot-clé
|
||||
- Support multi-fichiers
|
||||
|
||||
### Whitelist
|
||||
- Ajouter des joueurs
|
||||
- Supprimer des joueurs
|
||||
- Format JSON automatique
|
||||
|
||||
### Backups
|
||||
- Création manuelle
|
||||
- Compression tar.gz
|
||||
- Gestion de l'espace
|
||||
|
||||
### Paramètres
|
||||
- Lire tous les paramètres server.properties
|
||||
- Modifier certains paramètres
|
||||
- Changement de RCON
|
||||
|
||||
## 🔒 Sécurité - Important!
|
||||
|
||||
### En Production:
|
||||
|
||||
1. **Changez SESSION_SECRET**:
|
||||
```bash
|
||||
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
||||
```
|
||||
|
||||
2. **Utilisez HTTPS**:
|
||||
- Achetez un certificat SSL
|
||||
- Configurez un proxy (nginx/Apache)
|
||||
- Voir DEPLOYMENT.md
|
||||
|
||||
3. **Limitez l'accès**:
|
||||
- Firewall: autoriser seulement vos IPs
|
||||
- VPN: si accès distant
|
||||
- Proxy avec authentification
|
||||
|
||||
4. **Changez RCON**:
|
||||
- Utilisez un mot de passe fort
|
||||
- Changez-le régulièrement
|
||||
|
||||
5. **Backups**:
|
||||
- Sauvegardez vos backups ailleurs
|
||||
- Testez les restaurations
|
||||
|
||||
## 📞 Support et Aide
|
||||
|
||||
### Vérification Rapide
|
||||
|
||||
```bash
|
||||
# Vérifier que Node.js fonctionne
|
||||
node --version
|
||||
|
||||
# Tester npm
|
||||
npm --version
|
||||
|
||||
# Vérifier RCON du serveur MC
|
||||
echo "status" | nc localhost 25575
|
||||
|
||||
# Vérifier les logs
|
||||
cat /path/to/mc-server/latest.log | tail -50
|
||||
```
|
||||
|
||||
### Logs de l'Application
|
||||
|
||||
Les logs du backend s'affichent en direct quand vous lancez `npm start`.
|
||||
|
||||
Logs des commandes RCON:
|
||||
```
|
||||
SERVER_DIR/.web-admin/rcon-history.json
|
||||
```
|
||||
|
||||
## 🚀 Prochaines Étapes
|
||||
|
||||
- [ ] Tester toutes les fonctionnalités
|
||||
- [ ] Ajouter des joueurs à la whitelist
|
||||
- [ ] Créer des backups réguliers
|
||||
- [ ] Configurer HTTPS pour la production
|
||||
- [ ] Automatiser avec des scripts
|
||||
|
||||
## 📝 Notes Importantes
|
||||
|
||||
1. **Redémarrage du serveur**: Les joueurs seront déconnectés
|
||||
2. **Sauvegarde**: Faites des backups réguliers
|
||||
3. **RCON**: Gardez le mot de passe sécurisé
|
||||
4. **Logs**: Vérifiez régulièrement pour les erreurs
|
||||
5. **Mises à jour**: Vérifiez les mises à jour Node.js
|
||||
|
||||
Bon courage avec votre serveur! 🎮
|
||||
64
DEPLOYMENT.md
Normal file
64
DEPLOYMENT.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Proxy Configuration pour Production
|
||||
# Utilisez ce fichier comme base pour configurer votre serveur web
|
||||
|
||||
# NGINX Configuration Example
|
||||
# ==========================
|
||||
|
||||
# server {
|
||||
# listen 80;
|
||||
# server_name admin.nationglory.com;
|
||||
#
|
||||
# # Redirect to HTTPS
|
||||
# return 301 https://$server_name$request_uri;
|
||||
# }
|
||||
#
|
||||
# server {
|
||||
# listen 443 ssl http2;
|
||||
# server_name admin.nationglory.com;
|
||||
#
|
||||
# ssl_certificate /etc/ssl/certs/your-cert.crt;
|
||||
# ssl_certificate_key /etc/ssl/private/your-key.key;
|
||||
#
|
||||
# client_max_body_size 100M;
|
||||
#
|
||||
# location / {
|
||||
# proxy_pass http://localhost:3000;
|
||||
# proxy_http_version 1.1;
|
||||
# proxy_set_header Upgrade $http_upgrade;
|
||||
# proxy_set_header Connection 'upgrade';
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_cache_bypass $http_upgrade;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# }
|
||||
# }
|
||||
|
||||
# Apache Configuration Example
|
||||
# ============================
|
||||
|
||||
# <VirtualHost *:80>
|
||||
# ServerName admin.nationglory.com
|
||||
# Redirect permanent / https://admin.nationglory.com/
|
||||
# </VirtualHost>
|
||||
#
|
||||
# <VirtualHost *:443>
|
||||
# ServerName admin.nationglory.com
|
||||
# SSLEngine on
|
||||
# SSLCertificateFile /etc/ssl/certs/your-cert.crt
|
||||
# SSLCertificateKeyFile /etc/ssl/private/your-key.key
|
||||
#
|
||||
# ProxyPreserveHost On
|
||||
# ProxyPass / http://localhost:3000/
|
||||
# ProxyPassReverse / http://localhost:3000/
|
||||
#
|
||||
# RequestHeader set X-Forwarded-Proto "https"
|
||||
# RequestHeader set X-Forwarded-Port "443"
|
||||
# </VirtualHost>
|
||||
|
||||
# Environment Variables
|
||||
# ======================
|
||||
# Set these in production:
|
||||
# NODE_ENV=production
|
||||
# SESSION_SECRET=use-a-strong-random-key-here
|
||||
# PORT=3000 (internal, proxy on 80/443)
|
||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY backend/package*.json ./
|
||||
|
||||
RUN npm ci --only=production
|
||||
|
||||
COPY backend/src ./src
|
||||
COPY frontend ./frontend
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "src/server.js"]
|
||||
244
INDEX.md
Normal file
244
INDEX.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# 📋 Index - Fichiers et Documentation
|
||||
|
||||
## 🗂️ Structure du Projet
|
||||
|
||||
```
|
||||
WebNationsGlory_ServeurBuild_Red/
|
||||
│
|
||||
├── 📄 Documentation
|
||||
│ ├── README.md ← Vue d'ensemble générale
|
||||
│ ├── QUICKSTART.md ← Démarrage rapide (5 min)
|
||||
│ ├── CONFIGURATION.md ← Configuration détaillée
|
||||
│ ├── MAINTENANCE.md ← Commandes et troubleshooting
|
||||
│ ├── DEPLOYMENT.md ← Production & HTTPS
|
||||
│ └── INDEX.md ← Ce fichier
|
||||
│
|
||||
├── 🚀 Scripts de Démarrage
|
||||
│ ├── setup.sh ← Préparation initiale
|
||||
│ ├── install.sh ← Installation des dépendances
|
||||
│ ├── start.sh ← Lancer l'application
|
||||
│ └── .gitignore ← Fichiers à ignorer Git
|
||||
│
|
||||
├── 📦 Backend (Express.js - Node.js)
|
||||
│ ├── package.json ← Dépendances Node.js
|
||||
│ ├── .env.example ← Template de configuration
|
||||
│ └── src/
|
||||
│ ├── server.js ← Point d'entrée principal
|
||||
│ ├── routes/
|
||||
│ │ ├── auth.js ← Authentification & login
|
||||
│ │ ├── rcon.js ← Console RCON & redémarrage
|
||||
│ │ ├── logs.js ← Gestion des logs
|
||||
│ │ ├── players.js ← Liste des joueurs
|
||||
│ │ ├── whitelist.js ← Gestion whitelist
|
||||
│ │ ├── backup.js ← Création de backups
|
||||
│ │ └── server.js ← Paramètres serveur
|
||||
│ └── utils/
|
||||
│ └── rcon.js ← Client RCON (protocole)
|
||||
│
|
||||
├── 🎨 Frontend (HTML/CSS/JavaScript)
|
||||
│ ├── package.json ← Meta frontend
|
||||
│ └── public/
|
||||
│ ├── index.html ← Page HTML principale
|
||||
│ ├── css/
|
||||
│ │ └── style.css ← Tous les styles
|
||||
│ └── js/
|
||||
│ └── app.js ← Application React-like
|
||||
│
|
||||
├── 🐳 Deployment
|
||||
│ ├── Dockerfile ← Build Docker
|
||||
│ └── docker-compose.yml ← Orchestration Docker
|
||||
│
|
||||
└── 🔧 Configuration
|
||||
├── .gitignore
|
||||
├── docker-compose.yml
|
||||
└── Dockerfile
|
||||
```
|
||||
|
||||
## 📖 Guides de Lecture
|
||||
|
||||
### Pour Démarrer Rapidement
|
||||
1. **QUICKSTART.md** - 5 minutes pour être opérationnel
|
||||
2. Suivez les 5 étapes simples
|
||||
|
||||
### Pour Comprendre la Configuration
|
||||
1. **README.md** - Vue d'ensemble
|
||||
2. **CONFIGURATION.md** - Configuration détaillée
|
||||
3. Éditer les fichiers .env
|
||||
|
||||
### Pour Utiliser l'Application
|
||||
1. Ouvrir http://localhost:3000
|
||||
2. Créer le compte admin
|
||||
3. Utiliser les onglets du dashboard
|
||||
|
||||
### Pour Administrer le Serveur
|
||||
1. **MAINTENANCE.md** - Commandes utiles
|
||||
2. Sauvegardes régulières
|
||||
3. Monitoring
|
||||
|
||||
### Pour la Production
|
||||
1. **DEPLOYMENT.md** - Configuration HTTPS/Proxy
|
||||
2. Sécurité renforcée
|
||||
3. Certificats SSL
|
||||
|
||||
## 🔑 Points Clés
|
||||
|
||||
### Configuration
|
||||
- **Backend .env**: Variables d'environnement critiques
|
||||
- **Server.properties**: RCON du serveur MC
|
||||
- **Ops.txt**: Liste des administrateurs
|
||||
|
||||
### Sécurité
|
||||
- SESSION_SECRET unique en production
|
||||
- HTTPS recommandé
|
||||
- Firewall bien configuré
|
||||
|
||||
### Maintenance
|
||||
- Backups réguliers
|
||||
- Logs à surveiller
|
||||
- Mises à jour Node.js
|
||||
|
||||
## 🚀 Quickstart Ultime
|
||||
|
||||
```bash
|
||||
# 1. Configuration serveur MC
|
||||
# Éditer server.properties:
|
||||
enable-rcon=true
|
||||
rcon.port=25575
|
||||
rcon.password=YourPassword123
|
||||
|
||||
# 2. Installation
|
||||
cd WebNationsGlory_ServeurBuild_Red
|
||||
./install.sh
|
||||
|
||||
# 3. Configuration app
|
||||
nano backend/.env
|
||||
|
||||
# 4. Lancement
|
||||
./start.sh
|
||||
|
||||
# 5. Accès
|
||||
# Ouvrir: http://localhost:3000
|
||||
```
|
||||
|
||||
## 📚 Fichiers Importants à Connaître
|
||||
|
||||
| Fichier | Rôle | Éditer? |
|
||||
|---------|------|--------|
|
||||
| backend/.env | Configuration | ✏️ OUI |
|
||||
| backend/src/server.js | Point d'entrée | ❌ Non |
|
||||
| frontend/public/js/app.js | Interface | ❌ Non (sauf custom) |
|
||||
| README.md | Documentation | ✏️ Opcional |
|
||||
| CONFIGURATION.md | Guide détaillé | ❌ Non |
|
||||
|
||||
## 🎯 Fonctionnalités par Fichier
|
||||
|
||||
### Routes API
|
||||
|
||||
**auth.js** 🔐
|
||||
- Création compte admin
|
||||
- Connexion/Déconnexion
|
||||
- Changement mot de passe RCON
|
||||
|
||||
**rcon.js** ⌨️
|
||||
- Exécution commandes RCON
|
||||
- Historique commandes
|
||||
- Redémarrage serveur
|
||||
- Sauvegarde monde
|
||||
|
||||
**logs.js** 📜
|
||||
- Visualisation logs
|
||||
- Recherche dans logs
|
||||
- Gestion fichiers logs
|
||||
|
||||
**players.js** 👥
|
||||
- Liste joueurs
|
||||
- UUID et connexions
|
||||
- Statistiques
|
||||
|
||||
**whitelist.js** ✅
|
||||
- Ajouter joueurs
|
||||
- Retirer joueurs
|
||||
- Gestion format
|
||||
|
||||
**backup.js** 💾
|
||||
- Créer backups
|
||||
- Lister backups
|
||||
- Supprimer backups
|
||||
|
||||
**server.js** ⚙️
|
||||
- Lire paramètres
|
||||
- Modifier paramètres
|
||||
- Configuration globale
|
||||
|
||||
### Utils
|
||||
|
||||
**rcon.js** 🔌
|
||||
- Client RCON (protocole Minecraft)
|
||||
- Connection/Auth
|
||||
- Envoi commandes
|
||||
- Parsing réponses
|
||||
|
||||
## 🔄 Flux de Requête
|
||||
|
||||
```
|
||||
Browser (frontend/public/js/app.js)
|
||||
↓
|
||||
HTTP Request
|
||||
↓
|
||||
Backend Express (backend/src/server.js)
|
||||
↓
|
||||
Route Handler (backend/src/routes/*.js)
|
||||
↓
|
||||
RCON Client (backend/src/utils/rcon.js)
|
||||
↓
|
||||
Serveur Minecraft
|
||||
↓
|
||||
Response → Backend → Browser
|
||||
```
|
||||
|
||||
## 🎓 Pour Apprendre
|
||||
|
||||
- **JavaScript**: app.js (client-side)
|
||||
- **Node.js**: server.js (backend)
|
||||
- **Express**: routes/*.js (API)
|
||||
- **RCON Protocol**: utils/rcon.js
|
||||
- **HTML/CSS**: index.html + style.css
|
||||
|
||||
## 🚨 Fichiers Critiques
|
||||
|
||||
Ne pas modifier sans savoir:
|
||||
- ✓ backend/src/utils/rcon.js - Protocole RCON
|
||||
- ✓ backend/src/server.js - Point d'entrée
|
||||
- ✓ backend/src/routes/auth.js - Sécurité
|
||||
|
||||
Peuvent être modifiés:
|
||||
- ✏️ frontend/public/css/style.css - Design
|
||||
- ✏️ frontend/public/js/app.js - Interface
|
||||
- ✏️ backend/.env - Configuration
|
||||
|
||||
## 📞 Ressources
|
||||
|
||||
- **Node.js Docs**: https://nodejs.org/docs
|
||||
- **Express Docs**: https://expressjs.com
|
||||
- **Minecraft Wiki RCON**: https://wiki.vg/RCON
|
||||
- **JavaScript MDN**: https://developer.mozilla.org
|
||||
|
||||
## ✅ Checklist d'Installation
|
||||
|
||||
- [ ] Node.js 14+ installé
|
||||
- [ ] ./install.sh exécuté
|
||||
- [ ] backend/.env configuré
|
||||
- [ ] Serveur MC avec RCON activé
|
||||
- [ ] Premier compte admin créé
|
||||
- [ ] Test RCON validé
|
||||
- [ ] Documentations lues
|
||||
|
||||
## 🎉 Vous êtes Prêt!
|
||||
|
||||
Profitez de votre interface d'administration! 🚀
|
||||
|
||||
Pour plus d'aide, consultez:
|
||||
1. QUICKSTART.md (5 min)
|
||||
2. CONFIGURATION.md (détails)
|
||||
3. MAINTENANCE.md (commandes)
|
||||
4. DEPLOYMENT.md (production)
|
||||
347
MAINTENANCE.md
Normal file
347
MAINTENANCE.md
Normal file
@@ -0,0 +1,347 @@
|
||||
# 🔧 Maintenance et Administration
|
||||
|
||||
## Commandes de Base
|
||||
|
||||
### Démarrer l'application
|
||||
```bash
|
||||
cd WebNationsGlory_ServeurBuild_Red
|
||||
./start.sh
|
||||
```
|
||||
|
||||
### Arrêter l'application
|
||||
```bash
|
||||
# Ctrl+C dans le terminal
|
||||
```
|
||||
|
||||
### Redémarrer l'application
|
||||
```bash
|
||||
# 1. Arrêtez avec Ctrl+C
|
||||
# 2. Relancez avec ./start.sh
|
||||
```
|
||||
|
||||
## Dépannage
|
||||
|
||||
### Vérifier l'état de RCON
|
||||
```bash
|
||||
# Test simple
|
||||
echo "status" | timeout 2 bash -c 'exec 3<>/dev/tcp/localhost/25575; cat >&3; cat <&3' 2>/dev/null || echo "RCON non accessible"
|
||||
|
||||
# Avec nc (netcat)
|
||||
nc -w 2 localhost 25575 < /dev/null && echo "RCON OK" || echo "RCON KO"
|
||||
|
||||
# Avec telnet
|
||||
telnet localhost 25575
|
||||
```
|
||||
|
||||
### Vérifier les logs du serveur MC
|
||||
```bash
|
||||
# Dernières 50 lignes
|
||||
tail -50 /path/to/mc-server/latest.log
|
||||
|
||||
# Rechercher les erreurs
|
||||
grep ERROR /path/to/mc-server/latest.log
|
||||
|
||||
# Suivre en temps réel
|
||||
tail -f /path/to/mc-server/latest.log
|
||||
```
|
||||
|
||||
### Vérifier si le port 3000 est utilisé
|
||||
```bash
|
||||
# Linux/Mac
|
||||
lsof -i :3000
|
||||
|
||||
# Windows PowerShell
|
||||
Get-Process -Id (Get-NetTCPConnection -LocalPort 3000).OwningProcess
|
||||
|
||||
# Tuer le processus (Linux/Mac)
|
||||
kill -9 $(lsof -t -i :3000)
|
||||
```
|
||||
|
||||
### Réinitialiser Node.js
|
||||
```bash
|
||||
cd backend
|
||||
|
||||
# Supprimer node_modules
|
||||
rm -rf node_modules
|
||||
rm package-lock.json
|
||||
|
||||
# Réinstaller
|
||||
npm install
|
||||
|
||||
# Redémarrer
|
||||
npm start
|
||||
```
|
||||
|
||||
## Gestion des OPs
|
||||
|
||||
### Ajouter un OP sur le serveur MC
|
||||
```
|
||||
/op NomdujoueurMC
|
||||
```
|
||||
|
||||
### Vérifier les OPs
|
||||
```bash
|
||||
# Fichier ops.txt
|
||||
cat /path/to/mc-server/ops.txt
|
||||
|
||||
# Ou ops.json (Minecraft 1.8+)
|
||||
cat /path/to/mc-server/ops.json
|
||||
```
|
||||
|
||||
## Backups
|
||||
|
||||
### Créer un backup manuel
|
||||
```bash
|
||||
cd /path/to/mc-server
|
||||
|
||||
# Créer un tar.gz du serveur
|
||||
tar -czf ../backup-$(date +%Y%m%d-%H%M%S).tar.gz .
|
||||
|
||||
# Avec exclusion de certains fichiers
|
||||
tar -czf ../backup-$(date +%Y%m%d-%H%M%S).tar.gz \
|
||||
--exclude='*.log' \
|
||||
--exclude='cache' \
|
||||
--exclude='.web-admin' \
|
||||
.
|
||||
```
|
||||
|
||||
### Restaurer un backup
|
||||
```bash
|
||||
# Arrêter le serveur d'abord!
|
||||
./stop.sh
|
||||
|
||||
# Restaurer
|
||||
cd /backup/location
|
||||
tar -xzf backup-file.tar.gz -C /path/to/mc-server
|
||||
|
||||
# Redémarrer
|
||||
cd /path/to/mc-server
|
||||
./start.sh
|
||||
```
|
||||
|
||||
## Logs et Monitoring
|
||||
|
||||
### Voir les logs backend en temps réel
|
||||
```bash
|
||||
cd backend
|
||||
npm start
|
||||
```
|
||||
|
||||
### Voir les logs RCON (historique des commandes)
|
||||
```bash
|
||||
cat /path/to/mc-server/.web-admin/rcon-history.json
|
||||
```
|
||||
|
||||
### Nettoyer les vieux logs
|
||||
```bash
|
||||
# Supprimer les logs de plus de 30 jours
|
||||
find /path/to/mc-server -name "*.log" -mtime +30 -delete
|
||||
```
|
||||
|
||||
## Variables d'Environnement
|
||||
|
||||
### Changer le port
|
||||
```env
|
||||
# backend/.env
|
||||
PORT=8080
|
||||
```
|
||||
|
||||
### Changer le répertoire du serveur
|
||||
```env
|
||||
# backend/.env
|
||||
SERVER_DIR=/path/to/other/mc-server
|
||||
```
|
||||
|
||||
### Changer les identifiants RCON
|
||||
```env
|
||||
# backend/.env
|
||||
RCON_HOST=192.168.1.100
|
||||
RCON_PORT=25576
|
||||
```
|
||||
|
||||
## Mise à Jour des Dépendances
|
||||
|
||||
### Vérifier les mises à jour disponibles
|
||||
```bash
|
||||
cd backend
|
||||
npm outdated
|
||||
```
|
||||
|
||||
### Mettre à jour les dépendances
|
||||
```bash
|
||||
cd backend
|
||||
npm update
|
||||
```
|
||||
|
||||
### Mettre à jour une dépendance spécifique
|
||||
```bash
|
||||
cd backend
|
||||
npm install express@latest
|
||||
```
|
||||
|
||||
## Sécurité
|
||||
|
||||
### Changer le SESSION_SECRET
|
||||
```bash
|
||||
# Générer une nouvelle clé
|
||||
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
||||
|
||||
# Mettre à jour dans backend/.env
|
||||
SESSION_SECRET=<nouvelle-cle-generee>
|
||||
|
||||
# Redémarrer l'application
|
||||
```
|
||||
|
||||
### Changer le mot de passe RCON
|
||||
```bash
|
||||
# Via l'interface web: Dashboard → Changer RCON
|
||||
# Ou éditer manuellement server.properties:
|
||||
rcon.password=NewPassword123
|
||||
|
||||
# Redémarrer le serveur MC
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Vérifier l'utilisation mémoire Node.js
|
||||
```bash
|
||||
ps aux | grep node
|
||||
|
||||
# Linux avec plus de détails
|
||||
ps -eo pid,vsz,rss,comm | grep node
|
||||
```
|
||||
|
||||
### Nettoyer les fichiers temporaires
|
||||
```bash
|
||||
cd backend
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### L'application démarre mais l'interface ne charge pas
|
||||
```bash
|
||||
# Vérifier que le frontend est bien servi
|
||||
curl http://localhost:3000/
|
||||
# Doit retourner l'HTML
|
||||
|
||||
# Vérifier la console du navigateur (F12) pour les erreurs
|
||||
```
|
||||
|
||||
### Les commandes RCON n'exécutent pas
|
||||
```bash
|
||||
# Vérifier que RCON est activé
|
||||
grep "enable-rcon" /path/to/mc-server/server.properties
|
||||
# Doit être: enable-rcon=true
|
||||
|
||||
# Vérifier le mot de passe RCON
|
||||
grep "rcon.password" /path/to/mc-server/server.properties
|
||||
|
||||
# Tester RCON directement
|
||||
echo "say Test" | nc localhost 25575
|
||||
```
|
||||
|
||||
### Base de données de sessions corruptée
|
||||
```bash
|
||||
# Supprimer les sessions
|
||||
rm -rf backend/sessions/*
|
||||
|
||||
# Redémarrer l'application
|
||||
./start.sh
|
||||
```
|
||||
|
||||
## Commandes Minecraft Utiles
|
||||
|
||||
### Via la console web
|
||||
|
||||
```
|
||||
/list # Liste les joueurs
|
||||
/say @a Message # Message à tous
|
||||
/tp @p @s # Téléporter
|
||||
/weather clear # Météo
|
||||
/time set day # Heure
|
||||
/gamerule <règle> # Game rules
|
||||
/difficulty 3 # Difficulté
|
||||
/seed # Seed du monde
|
||||
/spawnpoint @p 0 100 0 # Spawnpoint
|
||||
```
|
||||
|
||||
## Scripts Automatisés
|
||||
|
||||
### Script de backup automatique
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# save-mc-backup.sh
|
||||
|
||||
SERVER_DIR="/path/to/mc-server"
|
||||
BACKUP_DIR="$SERVER_DIR/backups"
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.tar.gz"
|
||||
|
||||
cd "$SERVER_DIR"
|
||||
tar -czf "$BACKUP_FILE" \
|
||||
--exclude='*.log' \
|
||||
--exclude='cache' \
|
||||
--exclude='.web-admin' \
|
||||
.
|
||||
|
||||
echo "Backup créé: $BACKUP_FILE"
|
||||
|
||||
# À ajouter à crontab:
|
||||
# 0 2 * * * /path/to/save-mc-backup.sh
|
||||
```
|
||||
|
||||
### Script de redémarrage programmé
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# restart-mc-server.sh
|
||||
|
||||
echo "Redémarrage du serveur MC dans 1 minute..."
|
||||
|
||||
# Via RCON
|
||||
echo "say Redémarrage dans 1 minute!" | nc localhost 25575
|
||||
sleep 30
|
||||
echo "say 30 secondes!" | nc localhost 25575
|
||||
sleep 20
|
||||
echo "say 10 secondes!" | nc localhost 25575
|
||||
sleep 10
|
||||
echo "stop" | nc localhost 25575
|
||||
|
||||
sleep 5
|
||||
|
||||
# Redémarrer
|
||||
cd /path/to/mc-server
|
||||
./start.sh
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Vérifier l'espace disque
|
||||
```bash
|
||||
# Espace utilisé par le serveur
|
||||
du -sh /path/to/mc-server
|
||||
|
||||
# Espace libre
|
||||
df -h /path/to/mc-server
|
||||
```
|
||||
|
||||
### Vérifier les processus Node.js
|
||||
```bash
|
||||
ps aux | grep node
|
||||
# Ou
|
||||
ps aux | grep npm
|
||||
```
|
||||
|
||||
### Vérifier les ports en écoute
|
||||
```bash
|
||||
netstat -tlnp | grep 3000 # Node.js
|
||||
netstat -tlnp | grep 25575 # RCON
|
||||
netstat -tlnp | grep 25565 # Serveur MC
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**💡 Astuce**: Sauvegardez ces commandes pour un accès rapide!
|
||||
136
QUICKSTART.md
Normal file
136
QUICKSTART.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# 🎯 Guide Rapide - Démarrage en 5 Minutes
|
||||
|
||||
## Prérequis
|
||||
- Node.js 14+ installé
|
||||
- Serveur Minecraft avec RCON activé
|
||||
- Vous devez être OP sur le serveur
|
||||
|
||||
## ⚡ Installation Express
|
||||
|
||||
### 1️⃣ Configuration du serveur MC (10 secondes)
|
||||
|
||||
Éditer le fichier `server.properties` du serveur Minecraft:
|
||||
|
||||
```properties
|
||||
enable-rcon=true
|
||||
rcon.port=25575
|
||||
rcon.password=YourPassword123
|
||||
```
|
||||
|
||||
Redémarrer le serveur MC.
|
||||
|
||||
### 2️⃣ Installation de l'app (2 minutes)
|
||||
|
||||
```bash
|
||||
cd /home/innotex/Documents/Projet/Serveur\ NationsGlory/WebNationsGlory_ServeurBuild_Red
|
||||
|
||||
# Installation automatique
|
||||
./install.sh
|
||||
```
|
||||
|
||||
### 3️⃣ Configuration (1 minute)
|
||||
|
||||
```bash
|
||||
# Éditer le fichier
|
||||
nano backend/.env
|
||||
```
|
||||
|
||||
Vérifiez:
|
||||
```env
|
||||
PORT=3000
|
||||
SERVER_DIR=/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
|
||||
RCON_HOST=localhost
|
||||
RCON_PORT=25575
|
||||
```
|
||||
|
||||
### 4️⃣ Lancement (30 secondes)
|
||||
|
||||
```bash
|
||||
./start.sh
|
||||
```
|
||||
|
||||
Vous devriez voir:
|
||||
```
|
||||
🚀 Backend Admin NationsGlory démarré sur http://localhost:3000
|
||||
```
|
||||
|
||||
### 5️⃣ Accès (10 secondes)
|
||||
|
||||
Ouvrez: **http://localhost:3000**
|
||||
|
||||
## 👤 Premier Compte
|
||||
|
||||
1. Remplissez le formulaire d'enregistrement:
|
||||
- Nom d'utilisateur: `admin` (ou ce que vous voulez)
|
||||
- Mot de passe: Votre mot de passe
|
||||
- Pseudo Minecraft: **VOTRE NOM DE JOUEUR** (doit être OP!)
|
||||
|
||||
2. Cliquez "Créer le compte"
|
||||
|
||||
3. Connectez-vous avec vos identifiants
|
||||
|
||||
## ✅ Test Rapide
|
||||
|
||||
1. Allez à l'onglet **Console RCON**
|
||||
2. Tapez: `/time query daytime`
|
||||
3. Cliquez "Envoyer"
|
||||
4. Vous devriez voir l'heure du serveur
|
||||
|
||||
Si ça marche = ✓ Tout est bon!
|
||||
|
||||
## 🎮 Commandes Utiles
|
||||
|
||||
```
|
||||
/say Bienvenue! → Message à tous
|
||||
/tp @s 0 100 0 → Se téléporter
|
||||
/give @p diamond 64 → Donner des items
|
||||
/weather clear → Météo
|
||||
/difficulty 3 → Difficulté
|
||||
/time set day → Midi
|
||||
```
|
||||
|
||||
## ⚠️ Problèmes Courants
|
||||
|
||||
| Problème | Solution |
|
||||
|----------|----------|
|
||||
| "Timeout RCON" | Vérifier que le serveur MC est en ligne |
|
||||
| "Mot de passe incorrect" | Vérifier dans server.properties |
|
||||
| "Joueur n'est pas OP" | Faire `/op NomDuJoueur` sur le serveur MC |
|
||||
| "Impossible de se connecter" | Vérifier que `npm start` est lancé |
|
||||
|
||||
## 📁 Fichiers Importants
|
||||
|
||||
```
|
||||
backend/
|
||||
├── .env ← Configuration (à modifier)
|
||||
├── package.json ← Dépendances
|
||||
└── src/
|
||||
├── server.js ← Point d'entrée
|
||||
└── routes/ ← API endpoints
|
||||
|
||||
frontend/
|
||||
└── public/
|
||||
└── index.html ← Interface web
|
||||
```
|
||||
|
||||
## 🔐 Sécurité Basique
|
||||
|
||||
1. Changez `SESSION_SECRET` dans `.env` (ligne importante)
|
||||
2. Utilisez un mot de passe RCON fort
|
||||
3. Ne partagez pas vos identifiants
|
||||
|
||||
## 📖 Documentation Complète
|
||||
|
||||
- **README.md** - Vue d'ensemble complète
|
||||
- **CONFIGURATION.md** - Configuration détaillée
|
||||
- **DEPLOYMENT.md** - Déploiement en production
|
||||
|
||||
## 🆘 Besoin d'aide?
|
||||
|
||||
1. Vérifiez les logs: `npm start` affiche tout
|
||||
2. Testez RCON: `echo "status" | nc localhost 25575`
|
||||
3. Lisez CONFIGURATION.md pour plus de détails
|
||||
|
||||
---
|
||||
|
||||
**Vous êtes prêt!** 🚀 Profitez de votre interface!
|
||||
228
README.md
Normal file
228
README.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# Interface Web d'Administration - NationsGlory
|
||||
|
||||
Application web complète pour gérer votre serveur Minecraft 1.6.4 sans taper une seule commande!
|
||||
|
||||
## 🚀 Fonctionnalités
|
||||
|
||||
### 🔐 Sécurité
|
||||
- Authentification sécurisée par mot de passe
|
||||
- Seuls les OPs du serveur MC peuvent se connecter
|
||||
- Sessions sécurisées avec expiration
|
||||
|
||||
### ⌨️ Console RCON
|
||||
- Exécutez des commandes directement depuis l'interface
|
||||
- Historique complet des commandes
|
||||
- Réponses en temps réel
|
||||
|
||||
### 📊 Dashboard
|
||||
- État du serveur en temps réel
|
||||
- Nombre de joueurs connectés
|
||||
- Informations rapides du serveur
|
||||
|
||||
### 📜 Logs
|
||||
- Visualisation des logs en temps réel
|
||||
- Recherche dans les logs
|
||||
- Plusieurs fichiers logs supportés
|
||||
|
||||
### 👥 Gestion des Joueurs
|
||||
- Liste de tous les joueurs ayant joué
|
||||
- UUID et dernière connexion
|
||||
- Informations détaillées
|
||||
|
||||
### ✅ Whitelist
|
||||
- Ajouter/supprimer des joueurs
|
||||
- Gestion complète de la whitelist
|
||||
- Support des formats JSON et TXT
|
||||
|
||||
### 💾 Backups
|
||||
- Création de backups automatiques
|
||||
- Gestion des anciens backups
|
||||
- Suppression facile
|
||||
|
||||
### ⚙️ Paramètres
|
||||
- Modification des settings du serveur
|
||||
- Changement du mot de passe RCON
|
||||
- Gestion complète de server.properties
|
||||
|
||||
### 🔄 Commandes Utiles
|
||||
- Redémarrage du serveur
|
||||
- Sauvegarde du monde
|
||||
- Notifications aux joueurs
|
||||
|
||||
## 📋 Prérequis
|
||||
|
||||
- Node.js 14+ et npm
|
||||
- Serveur Minecraft avec RCON activé
|
||||
- RCON correctement configuré dans server.properties
|
||||
|
||||
## 🔧 Installation
|
||||
|
||||
### 1. Préparer le serveur Minecraft
|
||||
|
||||
Éditer `server.properties` du serveur MC:
|
||||
```properties
|
||||
enable-rcon=true
|
||||
rcon.port=25575
|
||||
rcon.password=votreMotDePasseRcon
|
||||
```
|
||||
|
||||
Redémarrer le serveur MC.
|
||||
|
||||
### 2. Installer l'interface web
|
||||
|
||||
```bash
|
||||
cd WebNationsGlory_ServeurBuild_Red
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
### 3. Configurer
|
||||
|
||||
Éditer `backend/.env`:
|
||||
```env
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
SESSION_SECRET=changez-cette-cle-en-production
|
||||
SERVER_DIR=/path/to/NationsGlory_ServeurBuild_Red
|
||||
RCON_HOST=localhost
|
||||
RCON_PORT=25575
|
||||
```
|
||||
|
||||
### 4. Lancer l'application
|
||||
|
||||
```bash
|
||||
chmod +x start.sh
|
||||
./start.sh
|
||||
```
|
||||
|
||||
L'interface est maintenant accessible sur: **http://localhost:3000**
|
||||
|
||||
## 🎯 Premier Démarrage
|
||||
|
||||
1. Ouvrez http://localhost:3000
|
||||
2. Créez le compte admin (doit être un OP du serveur)
|
||||
3. Connectez-vous
|
||||
4. Explorez l'interface!
|
||||
|
||||
## 📁 Structure du Projet
|
||||
|
||||
```
|
||||
WebNationsGlory_ServeurBuild_Red/
|
||||
├── backend/
|
||||
│ ├── package.json
|
||||
│ ├── .env (à créer)
|
||||
│ └── src/
|
||||
│ ├── server.js (point d'entrée)
|
||||
│ ├── routes/
|
||||
│ │ ├── auth.js (authentification)
|
||||
│ │ ├── rcon.js (console & contrôle)
|
||||
│ │ ├── logs.js (visualisation logs)
|
||||
│ │ ├── players.js (gestion joueurs)
|
||||
│ │ ├── whitelist.js (gestion whitelist)
|
||||
│ │ ├── backup.js (gestion backups)
|
||||
│ │ └── server.js (paramètres)
|
||||
│ └── utils/
|
||||
│ └── rcon.js (client RCON)
|
||||
├── frontend/
|
||||
│ └── public/
|
||||
│ ├── index.html
|
||||
│ ├── js/app.js
|
||||
│ └── css/style.css
|
||||
└── start.sh
|
||||
```
|
||||
|
||||
## 🔌 API Endpoints
|
||||
|
||||
### Authentification
|
||||
- `POST /api/auth/register` - Créer un compte admin
|
||||
- `POST /api/auth/login` - Se connecter
|
||||
- `GET /api/auth/check` - Vérifier la connexion
|
||||
- `POST /api/auth/logout` - Se déconnecter
|
||||
|
||||
### Console RCON
|
||||
- `POST /api/rcon/command` - Exécuter une commande
|
||||
- `GET /api/rcon/history` - Historique des commandes
|
||||
- `POST /api/rcon/restart` - Redémarrer le serveur
|
||||
- `POST /api/rcon/save` - Sauvegarder
|
||||
|
||||
### Serveur
|
||||
- `GET /api/server` - Récupérer les paramètres
|
||||
- `POST /api/server/update` - Mettre à jour un paramètre
|
||||
|
||||
### Logs
|
||||
- `GET /api/logs` - Récupérer les logs
|
||||
- `GET /api/logs/files` - Lister les fichiers logs
|
||||
- `GET /api/logs/file/:filename` - Lire un fichier spécifique
|
||||
- `GET /api/logs/search?query=...` - Chercher dans les logs
|
||||
|
||||
### Joueurs
|
||||
- `GET /api/players` - Lister les joueurs
|
||||
|
||||
### Whitelist
|
||||
- `GET /api/whitelist` - Récupérer la whitelist
|
||||
- `POST /api/whitelist/add` - Ajouter un joueur
|
||||
- `POST /api/whitelist/remove` - Retirer un joueur
|
||||
|
||||
### Backups
|
||||
- `GET /api/backup` - Lister les backups
|
||||
- `POST /api/backup/create` - Créer un backup
|
||||
- `POST /api/backup/delete/:filename` - Supprimer un backup
|
||||
|
||||
## 🆘 Dépannage
|
||||
|
||||
### La connexion RCON échoue
|
||||
- Vérifiez que RCON est activé dans server.properties
|
||||
- Vérifiez le port et le mot de passe
|
||||
- Assurez-vous que le serveur MC est en ligne
|
||||
|
||||
### Les logs ne s'affichent pas
|
||||
- Vérifiez le chemin de SERVER_DIR dans .env
|
||||
- Assurez-vous que les fichiers logs existent
|
||||
|
||||
### Les joueurs ne s'affichent pas
|
||||
- Attendez qu'au moins un joueur se soit connecté
|
||||
- Vérifiez que usercache.json existe
|
||||
|
||||
## ⚠️ Sécurité
|
||||
|
||||
En production:
|
||||
- Changez `SESSION_SECRET` avec une clé forte
|
||||
- Utilisez HTTPS
|
||||
- Placez derrière un proxy (nginx, Apache)
|
||||
- Limitez l'accès par IP si possible
|
||||
|
||||
## 📝 Logs Applicatifs
|
||||
|
||||
Les logs de l'application se trouvent dans:
|
||||
- Backend console: directement dans le terminal
|
||||
- RCON historique: `SERVER_DIR/.web-admin/rcon-history.json`
|
||||
|
||||
## 🎮 Commandes Minecraft Utiles
|
||||
|
||||
Depuis la console RCON:
|
||||
- `/say Message` - Envoyer un message à tous
|
||||
- `/tp @p @s` - Tp un joueur
|
||||
- `/give @p diamond 64` - Donner des items
|
||||
- `/weather clear` - Changer la météo
|
||||
- `/time set day` - Régler l'heure
|
||||
- `/difficulty 2` - Changer la difficulté
|
||||
|
||||
## 📞 Support
|
||||
|
||||
Pour les problèmes:
|
||||
1. Vérifiez la configuration du .env
|
||||
2. Vérifiez les logs du backend
|
||||
3. Testez RCON avec un client externe
|
||||
4. Vérifiez les permissions sur les fichiers du serveur
|
||||
|
||||
## 🔄 Mises à Jour
|
||||
|
||||
L'interface se met à jour automatiquement. Pour mettre à jour les dépendances:
|
||||
```bash
|
||||
cd backend
|
||||
npm update
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT - Libre d'utilisation
|
||||
6
backend/.env.example
Normal file
6
backend/.env.example
Normal file
@@ -0,0 +1,6 @@
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
SESSION_SECRET=your-very-secret-session-key-change-in-production
|
||||
SERVER_DIR=/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
|
||||
RCON_HOST=localhost
|
||||
RCON_PORT=25575
|
||||
25
backend/package.json
Normal file
25
backend/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "nations-glory-admin-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend API pour l'interface web de gestion du serveur Minecraft NationsGlory",
|
||||
"main": "src/server.js",
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"dev": "nodemon src/server.js"
|
||||
},
|
||||
"keywords": ["minecraft", "admin", "api"],
|
||||
"author": "Admin",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"dotenv": "^16.0.3",
|
||||
"cors": "^2.8.5",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"fs-extra": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.20"
|
||||
}
|
||||
}
|
||||
200
backend/src/routes/auth.js
Normal file
200
backend/src/routes/auth.js
Normal file
@@ -0,0 +1,200 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { SERVER_DIR } = require('../server');
|
||||
|
||||
const router = express.Router();
|
||||
const USERS_FILE = path.join(__dirname, '../../data/users.json');
|
||||
|
||||
async function initUsersFile() {
|
||||
await fs.ensureDir(path.dirname(USERS_FILE));
|
||||
if (!await fs.pathExists(USERS_FILE)) {
|
||||
await fs.writeJson(USERS_FILE, [], { spaces: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
function isAuthenticated(req, res, next) {
|
||||
if (req.session.user) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).json({ error: 'Non authentifié' });
|
||||
}
|
||||
}
|
||||
|
||||
async function getServerOps() {
|
||||
const opsFile = path.join(SERVER_DIR, 'ops.txt');
|
||||
try {
|
||||
if (await fs.pathExists(opsFile)) {
|
||||
const content = await fs.readFile(opsFile, 'utf-8');
|
||||
return content
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line && !line.startsWith('#'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur lecture ops.txt:', e);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async function getServerOpsJson() {
|
||||
const opsFile = path.join(SERVER_DIR, 'ops.json');
|
||||
try {
|
||||
if (await fs.pathExists(opsFile)) {
|
||||
const ops = await fs.readJson(opsFile);
|
||||
return ops.map(op => typeof op === 'string' ? op : op.name).filter(Boolean);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur lecture ops.json:', e);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
router.post('/register', async (req, res) => {
|
||||
try {
|
||||
await initUsersFile();
|
||||
const users = await fs.readJson(USERS_FILE);
|
||||
|
||||
if (users.length > 0) {
|
||||
return res.status(403).json({ error: 'Enregistrement déjà effectué' });
|
||||
}
|
||||
|
||||
const { username, password, mcUsername } = req.body;
|
||||
|
||||
if (!username || !password || !mcUsername) {
|
||||
return res.status(400).json({ error: 'Données manquantes' });
|
||||
}
|
||||
|
||||
const serverOps = await getServerOps();
|
||||
const serverOpsJson = await getServerOpsJson();
|
||||
const allOps = [...new Set([...serverOps, ...serverOpsJson])];
|
||||
|
||||
if (!allOps.includes(mcUsername)) {
|
||||
return res.status(403).json({
|
||||
error: 'Le joueur n\'est pas OP sur le serveur',
|
||||
availableOps: allOps
|
||||
});
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
users.push({
|
||||
id: Date.now().toString(),
|
||||
username,
|
||||
mcUsername,
|
||||
password: hashedPassword,
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
await fs.writeJson(USERS_FILE, users, { spaces: 2 });
|
||||
|
||||
res.json({
|
||||
message: 'Admin créé avec succès',
|
||||
user: { username, mcUsername }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur enregistrement:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
await initUsersFile();
|
||||
const users = await fs.readJson(USERS_FILE);
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: 'Identifiants manquants' });
|
||||
}
|
||||
|
||||
const user = users.find(u => u.username === username);
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Identifiants incorrects' });
|
||||
}
|
||||
|
||||
const passwordMatch = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!passwordMatch) {
|
||||
return res.status(401).json({ error: 'Identifiants incorrects' });
|
||||
}
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
mcUsername: user.mcUsername
|
||||
};
|
||||
|
||||
res.json({
|
||||
message: 'Connecté avec succès',
|
||||
user: req.session.user
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur connexion:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/check', (req, res) => {
|
||||
if (req.session.user) {
|
||||
res.json({ authenticated: true, user: req.session.user });
|
||||
} else {
|
||||
res.json({ authenticated: false });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy((err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Erreur déconnexion' });
|
||||
}
|
||||
res.json({ message: 'Déconnecté' });
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/change-rcon-password', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const { oldPassword, newPassword } = req.body;
|
||||
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
|
||||
|
||||
if (!oldPassword || !newPassword) {
|
||||
return res.status(400).json({ error: 'Données manquantes' });
|
||||
}
|
||||
|
||||
let content = await fs.readFile(serverPropsFile, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
let currentPassword = null;
|
||||
|
||||
for (let line of lines) {
|
||||
if (line.startsWith('rcon.password=')) {
|
||||
currentPassword = line.split('=')[1].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentPassword !== oldPassword) {
|
||||
return res.status(401).json({ error: 'Ancien mot de passe RCON incorrect' });
|
||||
}
|
||||
|
||||
content = lines.map(line => {
|
||||
if (line.startsWith('rcon.password=')) {
|
||||
return `rcon.password=${newPassword}`;
|
||||
}
|
||||
return line;
|
||||
}).join('\n');
|
||||
|
||||
await fs.writeFile(serverPropsFile, content, 'utf-8');
|
||||
|
||||
res.json({ message: 'Mot de passe RCON changé (redémarrage du serveur requis)' });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur changement RCON:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
99
backend/src/routes/backup.js
Normal file
99
backend/src/routes/backup.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { exec } = require('child_process');
|
||||
const { SERVER_DIR } = require('../server');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function isAuthenticated(req, res, next) {
|
||||
if (req.session.user) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).json({ error: 'Non authentifié' });
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const backupDir = path.join(SERVER_DIR, 'backups');
|
||||
|
||||
if (!await fs.pathExists(backupDir)) {
|
||||
return res.json({ backups: [] });
|
||||
}
|
||||
|
||||
const files = await fs.readdir(backupDir);
|
||||
const backups = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const filePath = path.join(backupDir, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
return {
|
||||
name: file,
|
||||
size: (stats.size / (1024 * 1024)).toFixed(2) + ' MB',
|
||||
created: stats.mtime,
|
||||
path: filePath
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
res.json(backups.sort((a, b) => new Date(b.created) - new Date(a.created)));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur backups:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/create', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const backupDir = path.join(SERVER_DIR, 'backups');
|
||||
await fs.ensureDir(backupDir);
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const backupName = `backup-${timestamp}.tar.gz`;
|
||||
const backupPath = path.join(backupDir, backupName);
|
||||
|
||||
res.json({ message: 'Backup en cours...', backupName });
|
||||
|
||||
// Exécuter la création de backup en arrière-plan
|
||||
exec(
|
||||
`cd "${SERVER_DIR}" && tar --exclude='.web-admin' --exclude='*.log.lck' -czf "${backupPath}" . 2>/dev/null`,
|
||||
(error) => {
|
||||
if (error) {
|
||||
console.error('Erreur création backup:', error);
|
||||
} else {
|
||||
console.log(`✓ Backup créé: ${backupName}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur création backup:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/delete/:filename', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const { filename } = req.params;
|
||||
|
||||
if (filename.includes('..')) {
|
||||
return res.status(400).json({ error: 'Accès non autorisé' });
|
||||
}
|
||||
|
||||
const backupPath = path.join(SERVER_DIR, 'backups', filename);
|
||||
|
||||
if (!await fs.pathExists(backupPath)) {
|
||||
return res.status(404).json({ error: 'Fichier non trouvé' });
|
||||
}
|
||||
|
||||
await fs.remove(backupPath);
|
||||
res.json({ message: 'Backup supprimé' });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur suppression backup:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
138
backend/src/routes/logs.js
Normal file
138
backend/src/routes/logs.js
Normal file
@@ -0,0 +1,138 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { SERVER_DIR } = require('../server');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function isAuthenticated(req, res, next) {
|
||||
if (req.session.user) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).json({ error: 'Non authentifié' });
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const { lines = 100 } = req.query;
|
||||
const logsDir = SERVER_DIR;
|
||||
|
||||
let logFile = path.join(logsDir, 'latest.log');
|
||||
|
||||
if (!await fs.pathExists(logFile)) {
|
||||
logFile = path.join(logsDir, 'ForgeModLoader-server-0.log');
|
||||
}
|
||||
|
||||
if (!await fs.pathExists(logFile)) {
|
||||
return res.json({ logs: [], message: 'Aucun fichier log trouvé' });
|
||||
}
|
||||
|
||||
const content = await fs.readFile(logFile, 'utf-8');
|
||||
const logLines = content.split('\n');
|
||||
const lastLines = logLines.slice(-parseInt(lines));
|
||||
|
||||
res.json({
|
||||
logs: lastLines,
|
||||
file: path.basename(logFile),
|
||||
totalLines: logLines.length
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur logs:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/files', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const logsDir = SERVER_DIR;
|
||||
const files = await fs.readdir(logsDir);
|
||||
|
||||
const logFiles = files.filter(f =>
|
||||
f.includes('log') || f.includes('Log')
|
||||
);
|
||||
|
||||
const filesWithStats = await Promise.all(
|
||||
logFiles.map(async (f) => {
|
||||
const stats = await fs.stat(path.join(logsDir, f));
|
||||
return {
|
||||
name: f,
|
||||
size: (stats.size / 1024).toFixed(2) + ' KB',
|
||||
modified: stats.mtime
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
res.json(filesWithStats.sort((a, b) => new Date(b.modified) - new Date(a.modified)));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur liste logs:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/file/:filename', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const { filename } = req.params;
|
||||
const { lines = 100 } = req.query;
|
||||
|
||||
if (filename.includes('..')) {
|
||||
return res.status(400).json({ error: 'Accès non autorisé' });
|
||||
}
|
||||
|
||||
const filePath = path.join(SERVER_DIR, filename);
|
||||
|
||||
if (!await fs.pathExists(filePath)) {
|
||||
return res.status(404).json({ error: 'Fichier non trouvé' });
|
||||
}
|
||||
|
||||
const content = await fs.readFile(filePath, 'utf-8');
|
||||
const fileLines = content.split('\n');
|
||||
const lastLines = fileLines.slice(-parseInt(lines));
|
||||
|
||||
res.json({
|
||||
logs: lastLines,
|
||||
file: filename,
|
||||
totalLines: fileLines.length
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur lecture log:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/search', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const { query } = req.query;
|
||||
|
||||
if (!query || query.length < 2) {
|
||||
return res.status(400).json({ error: 'Requête trop courte' });
|
||||
}
|
||||
|
||||
let logFile = path.join(SERVER_DIR, 'latest.log');
|
||||
if (!await fs.pathExists(logFile)) {
|
||||
logFile = path.join(SERVER_DIR, 'ForgeModLoader-server-0.log');
|
||||
}
|
||||
|
||||
if (!await fs.pathExists(logFile)) {
|
||||
return res.json({ results: [] });
|
||||
}
|
||||
|
||||
const content = await fs.readFile(logFile, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
const results = lines
|
||||
.map((line, index) => ({ line, index }))
|
||||
.filter(({ line }) => line.toLowerCase().includes(query.toLowerCase()))
|
||||
.slice(-50);
|
||||
|
||||
res.json({ results });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur recherche:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
62
backend/src/routes/players.js
Normal file
62
backend/src/routes/players.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { SERVER_DIR } = require('../server');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function isAuthenticated(req, res, next) {
|
||||
if (req.session.user) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).json({ error: 'Non authentifié' });
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const playersDir = path.join(SERVER_DIR, 'playerdata');
|
||||
const statsDir = path.join(SERVER_DIR, 'stats');
|
||||
|
||||
if (!await fs.pathExists(playersDir)) {
|
||||
return res.json({ players: [] });
|
||||
}
|
||||
|
||||
const files = await fs.readdir(playersDir);
|
||||
const uuidRegex = /^[0-9a-f-]{36}\.dat$/i;
|
||||
const playerFiles = files.filter(f => uuidRegex.test(f));
|
||||
|
||||
// Récupérer les noms des joueurs
|
||||
const usercacheFile = path.join(SERVER_DIR, 'usercache.json');
|
||||
let usercache = {};
|
||||
|
||||
if (await fs.pathExists(usercacheFile)) {
|
||||
try {
|
||||
const cache = await fs.readJson(usercacheFile);
|
||||
usercache = cache.reduce((acc, entry) => {
|
||||
acc[entry.uuid] = entry.name;
|
||||
return acc;
|
||||
}, {});
|
||||
} catch (e) {
|
||||
console.error('Erreur lecture usercache:', e);
|
||||
}
|
||||
}
|
||||
|
||||
const players = playerFiles.map(file => {
|
||||
const uuid = file.replace('.dat', '');
|
||||
return {
|
||||
uuid,
|
||||
name: usercache[uuid] || 'Inconnu',
|
||||
lastPlayed: new Date()
|
||||
};
|
||||
});
|
||||
|
||||
res.json({ players });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur joueurs:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
183
backend/src/routes/rcon.js
Normal file
183
backend/src/routes/rcon.js
Normal file
@@ -0,0 +1,183 @@
|
||||
const express = require('express');
|
||||
const RconClient = require('../utils/rcon');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { SERVER_DIR } = require('../server');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function isAuthenticated(req, res, next) {
|
||||
if (req.session.user) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).json({ error: 'Non authentifié' });
|
||||
}
|
||||
}
|
||||
|
||||
async function getRconConfig() {
|
||||
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
|
||||
try {
|
||||
const content = await fs.readFile(serverPropsFile, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
let config = { host: 'localhost', port: 25575, password: '' };
|
||||
|
||||
for (let line of lines) {
|
||||
if (line.startsWith('rcon.port=')) {
|
||||
config.port = parseInt(line.split('=')[1].trim());
|
||||
} else if (line.startsWith('rcon.password=')) {
|
||||
config.password = line.split('=')[1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
} catch (e) {
|
||||
console.error('Erreur lecture config RCON:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function addToHistory(command, response) {
|
||||
try {
|
||||
const historyFile = path.join(SERVER_DIR, '.web-admin/rcon-history.json');
|
||||
await fs.ensureDir(path.dirname(historyFile));
|
||||
|
||||
let history = [];
|
||||
if (await fs.pathExists(historyFile)) {
|
||||
history = await fs.readJson(historyFile);
|
||||
}
|
||||
|
||||
history.push({
|
||||
timestamp: new Date(),
|
||||
command,
|
||||
response: response.substring(0, 500)
|
||||
});
|
||||
|
||||
await fs.writeJson(historyFile, history.slice(-100), { spaces: 2 });
|
||||
} catch (e) {
|
||||
console.error('Erreur sauvegarde historique:', e);
|
||||
}
|
||||
}
|
||||
|
||||
router.post('/command', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const { command } = req.body;
|
||||
|
||||
if (!command) {
|
||||
return res.status(400).json({ error: 'Commande manquante' });
|
||||
}
|
||||
|
||||
const config = await getRconConfig();
|
||||
if (!config || !config.password) {
|
||||
return res.status(500).json({ error: 'Configuration RCON non trouvée' });
|
||||
}
|
||||
|
||||
const rcon = new RconClient(config.host, config.port, config.password);
|
||||
|
||||
try {
|
||||
await rcon.connect();
|
||||
const response = await rcon.sendCommand(command);
|
||||
rcon.disconnect();
|
||||
|
||||
await addToHistory(command, response);
|
||||
|
||||
res.json({ response, command });
|
||||
} catch (e) {
|
||||
console.error('Erreur RCON:', e);
|
||||
rcon.disconnect();
|
||||
res.status(500).json({ error: `Erreur RCON: ${e.message}` });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur commande:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/history', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const historyFile = path.join(SERVER_DIR, '.web-admin/rcon-history.json');
|
||||
|
||||
if (await fs.pathExists(historyFile)) {
|
||||
const history = await fs.readJson(historyFile);
|
||||
res.json(history);
|
||||
} else {
|
||||
res.json([]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur historique:', e);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/restart', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const config = await getRconConfig();
|
||||
if (!config || !config.password) {
|
||||
return res.status(500).json({ error: 'Configuration RCON non trouvée' });
|
||||
}
|
||||
|
||||
const rcon = new RconClient(config.host, config.port, config.password);
|
||||
|
||||
try {
|
||||
await rcon.connect();
|
||||
|
||||
await rcon.sendCommand('say §6[ADMIN] Serveur redémarrage dans 30 secondes!');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
await rcon.sendCommand('say §6[ADMIN] 25 secondes...');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
await rcon.sendCommand('say §6[ADMIN] 20 secondes...');
|
||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
||||
await rcon.sendCommand('say §6[ADMIN] Redémarrage maintenant!');
|
||||
|
||||
const response = await rcon.sendCommand('stop');
|
||||
rcon.disconnect();
|
||||
|
||||
await addToHistory('stop', response);
|
||||
|
||||
res.json({ message: 'Redémarrage en cours...' });
|
||||
} catch (e) {
|
||||
console.error('Erreur redémarrage:', e);
|
||||
rcon.disconnect();
|
||||
res.status(500).json({ error: `Erreur: ${e.message}` });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/save', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const config = await getRconConfig();
|
||||
if (!config || !config.password) {
|
||||
return res.status(500).json({ error: 'Configuration RCON non trouvée' });
|
||||
}
|
||||
|
||||
const rcon = new RconClient(config.host, config.port, config.password);
|
||||
|
||||
try {
|
||||
await rcon.connect();
|
||||
await rcon.sendCommand('say §6[ADMIN] Sauvegarde en cours...');
|
||||
const r1 = await rcon.sendCommand('save-off');
|
||||
const r2 = await rcon.sendCommand('save-all');
|
||||
const r3 = await rcon.sendCommand('save-on');
|
||||
await rcon.sendCommand('say §6[ADMIN] Sauvegarde terminée!');
|
||||
rcon.disconnect();
|
||||
|
||||
await addToHistory('save-all', r2);
|
||||
|
||||
res.json({ message: 'Sauvegarde effectuée' });
|
||||
} catch (e) {
|
||||
console.error('Erreur sauvegarde:', e);
|
||||
rcon.disconnect();
|
||||
res.status(500).json({ error: `Erreur: ${e.message}` });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
79
backend/src/routes/server.js
Normal file
79
backend/src/routes/server.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { SERVER_DIR } = require('../server');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function isAuthenticated(req, res, next) {
|
||||
if (req.session.user) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).json({ error: 'Non authentifié' });
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
|
||||
|
||||
if (!await fs.pathExists(serverPropsFile)) {
|
||||
return res.status(404).json({ error: 'server.properties non trouvé' });
|
||||
}
|
||||
|
||||
const content = await fs.readFile(serverPropsFile, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
const properties = {};
|
||||
|
||||
lines.forEach(line => {
|
||||
if (line && !line.startsWith('#')) {
|
||||
const [key, ...valueParts] = line.split('=');
|
||||
properties[key.trim()] = valueParts.join('=').trim();
|
||||
}
|
||||
});
|
||||
|
||||
res.json({ properties });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur serveur:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/update', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const { property, value } = req.body;
|
||||
|
||||
if (!property || value === undefined) {
|
||||
return res.status(400).json({ error: 'Données manquantes' });
|
||||
}
|
||||
|
||||
const serverPropsFile = path.join(SERVER_DIR, 'server.properties');
|
||||
let content = await fs.readFile(serverPropsFile, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
let found = false;
|
||||
const newContent = lines.map(line => {
|
||||
if (line.startsWith(property + '=')) {
|
||||
found = true;
|
||||
return `${property}=${value}`;
|
||||
}
|
||||
return line;
|
||||
}).join('\n');
|
||||
|
||||
if (!found) {
|
||||
content = newContent + '\n' + property + '=' + value;
|
||||
} else {
|
||||
content = newContent;
|
||||
}
|
||||
|
||||
await fs.writeFile(serverPropsFile, content, 'utf-8');
|
||||
res.json({ message: 'Propriété mise à jour' });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur mise à jour:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
123
backend/src/routes/whitelist.js
Normal file
123
backend/src/routes/whitelist.js
Normal file
@@ -0,0 +1,123 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { SERVER_DIR } = require('../server');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function isAuthenticated(req, res, next) {
|
||||
if (req.session.user) {
|
||||
next();
|
||||
} else {
|
||||
res.status(401).json({ error: 'Non authentifié' });
|
||||
}
|
||||
}
|
||||
|
||||
async function getWhitelistFile() {
|
||||
const jsonFile = path.join(SERVER_DIR, 'whitelist.json');
|
||||
const txtFile = path.join(SERVER_DIR, 'whitelist.txt');
|
||||
|
||||
if (await fs.pathExists(jsonFile)) {
|
||||
return jsonFile;
|
||||
} else if (await fs.pathExists(txtFile)) {
|
||||
return txtFile;
|
||||
}
|
||||
return jsonFile;
|
||||
}
|
||||
|
||||
router.get('/', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const whitelistFile = await getWhitelistFile();
|
||||
|
||||
if (!await fs.pathExists(whitelistFile)) {
|
||||
return res.json({ whitelist: [], format: 'json' });
|
||||
}
|
||||
|
||||
if (whitelistFile.endsWith('.json')) {
|
||||
const whitelist = await fs.readJson(whitelistFile);
|
||||
res.json({ whitelist, format: 'json' });
|
||||
} else {
|
||||
const content = await fs.readFile(whitelistFile, 'utf-8');
|
||||
const whitelist = content.split('\n').filter(line => line.trim());
|
||||
res.json({ whitelist, format: 'txt' });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur whitelist:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/add', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const { username } = req.body;
|
||||
|
||||
if (!username) {
|
||||
return res.status(400).json({ error: 'Nom manquant' });
|
||||
}
|
||||
|
||||
const whitelistFile = await getWhitelistFile();
|
||||
|
||||
if (whitelistFile.endsWith('.json')) {
|
||||
let whitelist = [];
|
||||
if (await fs.pathExists(whitelistFile)) {
|
||||
whitelist = await fs.readJson(whitelistFile);
|
||||
}
|
||||
|
||||
if (whitelist.some(w => (typeof w === 'string' ? w : w.name) === username)) {
|
||||
return res.status(400).json({ error: 'Joueur déjà en whitelist' });
|
||||
}
|
||||
|
||||
whitelist.push({ name: username, uuid: '' });
|
||||
await fs.writeJson(whitelistFile, whitelist, { spaces: 2 });
|
||||
} else {
|
||||
let content = '';
|
||||
if (await fs.pathExists(whitelistFile)) {
|
||||
content = await fs.readFile(whitelistFile, 'utf-8');
|
||||
}
|
||||
|
||||
if (content.includes(username)) {
|
||||
return res.status(400).json({ error: 'Joueur déjà en whitelist' });
|
||||
}
|
||||
|
||||
content += (content ? '\n' : '') + username;
|
||||
await fs.writeFile(whitelistFile, content, 'utf-8');
|
||||
}
|
||||
|
||||
res.json({ message: 'Joueur ajouté à la whitelist' });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur ajout whitelist:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/remove', isAuthenticated, async (req, res) => {
|
||||
try {
|
||||
const { username } = req.body;
|
||||
|
||||
if (!username) {
|
||||
return res.status(400).json({ error: 'Nom manquant' });
|
||||
}
|
||||
|
||||
const whitelistFile = await getWhitelistFile();
|
||||
|
||||
if (whitelistFile.endsWith('.json')) {
|
||||
let whitelist = await fs.readJson(whitelistFile);
|
||||
whitelist = whitelist.filter(w => (typeof w === 'string' ? w : w.name) !== username);
|
||||
await fs.writeJson(whitelistFile, whitelist, { spaces: 2 });
|
||||
} else {
|
||||
let content = await fs.readFile(whitelistFile, 'utf-8');
|
||||
content = content.split('\n').filter(line => line.trim() !== username).join('\n');
|
||||
await fs.writeFile(whitelistFile, content, 'utf-8');
|
||||
}
|
||||
|
||||
res.json({ message: 'Joueur retiré de la whitelist' });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur suppression whitelist:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
73
backend/src/server.js
Normal file
73
backend/src/server.js
Normal file
@@ -0,0 +1,73 @@
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv');
|
||||
const cors = require('cors');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 4001;
|
||||
const SERVER_DIR = process.env.SERVER_DIR || '/home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red';
|
||||
|
||||
// Middlewares
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(cors({
|
||||
origin: process.env.NODE_ENV === 'production' ? false : true,
|
||||
credentials: true
|
||||
}));
|
||||
|
||||
// Session config
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-prod',
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: true,
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 heures
|
||||
}
|
||||
}));
|
||||
|
||||
// Importation des routes
|
||||
const authRoutes = require('./routes/auth');
|
||||
const serverRoutes = require('./routes/server');
|
||||
const rconRoutes = require('./routes/rcon');
|
||||
const logsRoutes = require('./routes/logs');
|
||||
const playersRoutes = require('./routes/players');
|
||||
const whitelistRoutes = require('./routes/whitelist');
|
||||
const backupRoutes = require('./routes/backup');
|
||||
|
||||
// Routes API
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/server', serverRoutes);
|
||||
app.use('/api/rcon', rconRoutes);
|
||||
app.use('/api/logs', logsRoutes);
|
||||
app.use('/api/players', playersRoutes);
|
||||
app.use('/api/whitelist', whitelistRoutes);
|
||||
app.use('/api/backup', backupRoutes);
|
||||
|
||||
// Health check
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date() });
|
||||
});
|
||||
|
||||
// Erreur 404
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({ error: 'Route non trouvée' });
|
||||
});
|
||||
|
||||
// Error handler
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Erreur serveur interne' });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`\n🚀 Backend Admin NationsGlory démarré sur http://localhost:${PORT}`);
|
||||
console.log(`📁 Répertoire du serveur: ${SERVER_DIR}\n`);
|
||||
});
|
||||
|
||||
module.exports = { app, SERVER_DIR };
|
||||
141
backend/src/utils/rcon.js
Normal file
141
backend/src/utils/rcon.js
Normal file
@@ -0,0 +1,141 @@
|
||||
const net = require('net');
|
||||
|
||||
class RconClient {
|
||||
constructor(host, port, password) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.password = password;
|
||||
this.socket = null;
|
||||
this.authenticated = false;
|
||||
this.packetId = 0;
|
||||
this.responseBuffer = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
createPacket(type, payload) {
|
||||
const payloadBuffer = Buffer.from(payload, 'utf8');
|
||||
const body = Buffer.alloc(4 + payloadBuffer.length + 1);
|
||||
|
||||
body.writeInt32BE(type, 0);
|
||||
payloadBuffer.copy(body, 4);
|
||||
body[4 + payloadBuffer.length] = 0;
|
||||
|
||||
const size = body.length;
|
||||
const packet = Buffer.alloc(4 + size);
|
||||
packet.writeInt32LE(size, 0);
|
||||
body.copy(packet, 4);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
parsePacket(buffer) {
|
||||
if (buffer.length < 12) return null;
|
||||
|
||||
const size = buffer.readInt32LE(0);
|
||||
if (buffer.length < 4 + size) return null;
|
||||
|
||||
const id = buffer.readInt32LE(4);
|
||||
const type = buffer.readInt32LE(8);
|
||||
|
||||
let payload = '';
|
||||
if (size > 8) {
|
||||
payload = buffer.slice(12, 4 + size - 1).toString('utf8');
|
||||
}
|
||||
|
||||
return { id, type, payload, totalSize: 4 + size };
|
||||
}
|
||||
|
||||
async connect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket = net.createConnection({
|
||||
host: this.host,
|
||||
port: this.port
|
||||
});
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
console.log('✓ Connecté au serveur RCON');
|
||||
this.authenticate().then(resolve).catch(reject);
|
||||
});
|
||||
|
||||
this.socket.on('error', reject);
|
||||
|
||||
setTimeout(() => {
|
||||
reject(new Error('Timeout de connexion'));
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
async authenticate() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.packetId++;
|
||||
const packet = this.createPacket(3, this.password);
|
||||
|
||||
this.socket.write(packet);
|
||||
|
||||
const handleData = (data) => {
|
||||
this.responseBuffer = Buffer.concat([this.responseBuffer, data]);
|
||||
const response = this.parsePacket(this.responseBuffer);
|
||||
|
||||
if (response && response.id === this.packetId) {
|
||||
this.responseBuffer = this.responseBuffer.slice(response.totalSize);
|
||||
this.socket.removeListener('data', handleData);
|
||||
|
||||
if (response.id !== -1) {
|
||||
this.authenticated = true;
|
||||
console.log('✓ Authentifié RCON');
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error('Mot de passe RCON incorrect'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.socket.on('data', handleData);
|
||||
|
||||
setTimeout(() => {
|
||||
reject(new Error('Timeout d\'authentification'));
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
async sendCommand(command) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.authenticated) {
|
||||
reject(new Error('Non authentifié'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.packetId++;
|
||||
const id = this.packetId;
|
||||
const packet = this.createPacket(2, command);
|
||||
|
||||
this.socket.write(packet);
|
||||
|
||||
const handleData = (data) => {
|
||||
this.responseBuffer = Buffer.concat([this.responseBuffer, data]);
|
||||
const response = this.parsePacket(this.responseBuffer);
|
||||
|
||||
if (response && response.id === id) {
|
||||
this.responseBuffer = this.responseBuffer.slice(response.totalSize);
|
||||
this.socket.removeListener('data', handleData);
|
||||
resolve(response.payload);
|
||||
}
|
||||
};
|
||||
|
||||
this.socket.on('data', handleData);
|
||||
|
||||
setTimeout(() => {
|
||||
reject(new Error('Timeout de commande'));
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.socket) {
|
||||
this.socket.destroy();
|
||||
this.socket = null;
|
||||
this.authenticated = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RconClient;
|
||||
20
docker-compose.yml
Normal file
20
docker-compose.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "4001:4001"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 4001
|
||||
SERVER_DIR: /home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red
|
||||
RCON_HOST: host.docker.internal # ou votre adresse IP
|
||||
RCON_PORT: 25575
|
||||
SESSION_SECRET: change-this-in-production
|
||||
volumes:
|
||||
- /home/innotex/Documents/Projet/Serveur NationsGlory/NationsGlory_ServeurBuild_Red:/mc-server:ro
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
9
frontend/package.json
Normal file
9
frontend/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "nations-glory-admin-frontend",
|
||||
"version": "1.0.0",
|
||||
"description": "Frontend pour l'interface web de gestion Minecraft",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cd ../backend && npm run dev"
|
||||
}
|
||||
}
|
||||
484
frontend/public/css/style.css
Normal file
484
frontend/public/css/style.css
Normal file
@@ -0,0 +1,484 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #1e3c72;
|
||||
--secondary: #2a5298;
|
||||
--accent: #00d4ff;
|
||||
--danger: #ff6b6b;
|
||||
--success: #51cf66;
|
||||
--warning: #fcc419;
|
||||
--dark: #1a1a1a;
|
||||
--light: #f8f9fa;
|
||||
--text: #333;
|
||||
--border: #ddd;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Login */
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||||
}
|
||||
|
||||
.login-box {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.login-box h1 {
|
||||
color: var(--primary);
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||||
color: white;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(30, 60, 114, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--light);
|
||||
color: var(--primary);
|
||||
border: 2px solid var(--primary);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: var(--warning);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 12px 16px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background: rgba(81, 207, 102, 0.1);
|
||||
color: var(--success);
|
||||
border-left: 4px solid var(--success);
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background: rgba(255, 107, 107, 0.1);
|
||||
color: var(--danger);
|
||||
border-left: 4px solid var(--danger);
|
||||
}
|
||||
|
||||
/* Dashboard */
|
||||
.header {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: var(--primary);
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.user-info p {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.nav-tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.nav-tabs button {
|
||||
background: white;
|
||||
border: 2px solid var(--border);
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.nav-tabs button.active {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||||
color: white;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.nav-tabs button:hover:not(.active) {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Content Sections */
|
||||
.content-section {
|
||||
display: none;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
.content-section.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.section-card {
|
||||
background: white;
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.section-card h2 {
|
||||
color: var(--primary);
|
||||
margin-bottom: 20px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* Console */
|
||||
.console-output {
|
||||
background: #1e1e1e;
|
||||
color: #00ff00;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
font-family: 'Courier New', monospace;
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 15px;
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.console-output .error {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.console-output .warning {
|
||||
color: #fcc419;
|
||||
}
|
||||
|
||||
.console-input {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.console-input input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Logs Viewer */
|
||||
.logs-container {
|
||||
background: #1e1e1e;
|
||||
color: #00ff00;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
font-family: 'Courier New', monospace;
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--border);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logs-container .log-line {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.logs-container .error {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.logs-container .warning {
|
||||
color: #fcc419;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
table thead {
|
||||
background: var(--light);
|
||||
}
|
||||
|
||||
table th {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
table td {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
table tbody tr:hover {
|
||||
background: var(--light);
|
||||
}
|
||||
|
||||
/* Buttons Group */
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-group button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.grid-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.grid-card h3 {
|
||||
color: var(--primary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.grid-card .value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.modal-content h2 {
|
||||
color: var(--primary);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-content .form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Loading spinner */
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid rgba(0,0,0,0.1);
|
||||
border-top-color: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav-tabs button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Status indicators */
|
||||
.status {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.status.online {
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.status.offline {
|
||||
background: var(--danger);
|
||||
}
|
||||
|
||||
.status.busy {
|
||||
background: var(--warning);
|
||||
}
|
||||
13
frontend/public/index.html
Normal file
13
frontend/public/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NationsGlory Admin Panel</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
889
frontend/public/js/app.js
Normal file
889
frontend/public/js/app.js
Normal file
@@ -0,0 +1,889 @@
|
||||
// Configuration API
|
||||
const API_URL = 'http://localhost:3000/api';
|
||||
|
||||
// State
|
||||
let currentUser = null;
|
||||
let isAuthenticated = false;
|
||||
|
||||
// Initialisation
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
checkAuth();
|
||||
});
|
||||
|
||||
// Vérifier l'authentification
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/auth/check`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.authenticated) {
|
||||
currentUser = data.user;
|
||||
isAuthenticated = true;
|
||||
showDashboard();
|
||||
} else {
|
||||
showLoginPage();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur vérification auth:', e);
|
||||
showLoginPage();
|
||||
}
|
||||
}
|
||||
|
||||
// Afficher la page de connexion
|
||||
function showLoginPage() {
|
||||
document.getElementById('app').innerHTML = `
|
||||
<div class="login-container">
|
||||
<div class="login-box">
|
||||
<h1>🎮 NationsGlory Admin</h1>
|
||||
<div class="message" id="loginMessage"></div>
|
||||
|
||||
<div id="loginForm">
|
||||
<h2 style="text-align: center; margin-bottom: 20px; color: var(--primary); font-size: 18px;">Connexion</h2>
|
||||
<div class="form-group">
|
||||
<label>Nom d'utilisateur</label>
|
||||
<input type="text" id="username" placeholder="admin">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Mot de passe</label>
|
||||
<input type="password" id="password" placeholder="">
|
||||
</div>
|
||||
<button class="btn-primary" onclick="handleLogin()">Se connecter</button>
|
||||
</div>
|
||||
|
||||
<div id="registerForm" style="display: none;">
|
||||
<h2 style="text-align: center; margin-bottom: 20px; color: var(--primary); font-size: 18px;">Créer le compte Admin</h2>
|
||||
<div class="form-group">
|
||||
<label>Nom d'utilisateur</label>
|
||||
<input type="text" id="regUsername" placeholder="admin">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Mot de passe</label>
|
||||
<input type="password" id="regPassword" placeholder="">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Pseudo Minecraft (doit être OP)</label>
|
||||
<input type="text" id="mcUsername" placeholder="VotreNomMC">
|
||||
</div>
|
||||
<button class="btn-primary" onclick="handleRegister()">Créer le compte</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Vérifier s'il y a déjà un admin
|
||||
checkIfAdminExists();
|
||||
}
|
||||
|
||||
// Vérifier si un admin existe
|
||||
async function checkIfAdminExists() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/auth/check`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
// Si pas connecté, afficher le formulaire d'enregistrement si c'est la première fois
|
||||
setTimeout(() => {
|
||||
const regForm = document.getElementById('registerForm');
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
|
||||
if (regForm && loginForm) {
|
||||
// Pour la première connexion, montrer le formulaire d'enregistrement
|
||||
regForm.style.display = 'block';
|
||||
loginForm.style.display = 'none';
|
||||
}
|
||||
}, 100);
|
||||
} catch (e) {
|
||||
console.error('Erreur:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Connexion
|
||||
async function handleLogin() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
if (!username || !password) {
|
||||
showMessage('Veuillez remplir tous les champs', 'error', 'loginMessage');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
currentUser = data.user;
|
||||
isAuthenticated = true;
|
||||
showDashboard();
|
||||
} else {
|
||||
showMessage(data.error || 'Erreur de connexion', 'error', 'loginMessage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur connexion:', e);
|
||||
showMessage('Erreur serveur', 'error', 'loginMessage');
|
||||
}
|
||||
}
|
||||
|
||||
// Enregistrement
|
||||
async function handleRegister() {
|
||||
const username = document.getElementById('regUsername').value;
|
||||
const password = document.getElementById('regPassword').value;
|
||||
const mcUsername = document.getElementById('mcUsername').value;
|
||||
|
||||
if (!username || !password || !mcUsername) {
|
||||
showMessage('Veuillez remplir tous les champs', 'error', 'loginMessage');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ username, password, mcUsername })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
showMessage('Compte créé! Connectez-vous maintenant.', 'success', 'loginMessage');
|
||||
setTimeout(() => {
|
||||
document.getElementById('registerForm').style.display = 'none';
|
||||
document.getElementById('loginForm').style.display = 'block';
|
||||
document.getElementById('username').value = username;
|
||||
document.getElementById('password').value = '';
|
||||
}, 1000);
|
||||
} else {
|
||||
showMessage(data.error || 'Erreur d\'enregistrement', 'error', 'loginMessage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur enregistrement:', e);
|
||||
showMessage('Erreur serveur', 'error', 'loginMessage');
|
||||
}
|
||||
}
|
||||
|
||||
// Afficher le dashboard
|
||||
function showDashboard() {
|
||||
document.getElementById('app').innerHTML = `
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1>🎮 NationsGlory Admin Panel</h1>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<p>👤 <strong>${currentUser.username}</strong> (${currentUser.mcUsername})</p>
|
||||
<button class="btn-danger" onclick="handleLogout()">Déconnexion</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message" id="dashboardMessage"></div>
|
||||
|
||||
<div class="nav-tabs">
|
||||
<button class="active" onclick="switchTab('dashboard')">📊 Dashboard</button>
|
||||
<button onclick="switchTab('console')">⌨️ Console RCON</button>
|
||||
<button onclick="switchTab('logs')">📜 Logs</button>
|
||||
<button onclick="switchTab('players')">👥 Joueurs</button>
|
||||
<button onclick="switchTab('whitelist')">✅ Whitelist</button>
|
||||
<button onclick="switchTab('backups')">💾 Backups</button>
|
||||
<button onclick="switchTab('settings')">⚙️ Paramètres</button>
|
||||
</div>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<div id="dashboard" class="content-section active">
|
||||
${getDashboardHTML()}
|
||||
</div>
|
||||
|
||||
<!-- Console RCON -->
|
||||
<div id="console" class="content-section">
|
||||
${getConsoleHTML()}
|
||||
</div>
|
||||
|
||||
<!-- Logs -->
|
||||
<div id="logs" class="content-section">
|
||||
${getLogsHTML()}
|
||||
</div>
|
||||
|
||||
<!-- Joueurs -->
|
||||
<div id="players" class="content-section">
|
||||
${getPlayersHTML()}
|
||||
</div>
|
||||
|
||||
<!-- Whitelist -->
|
||||
<div id="whitelist" class="content-section">
|
||||
${getWhitelistHTML()}
|
||||
</div>
|
||||
|
||||
<!-- Backups -->
|
||||
<div id="backups" class="content-section">
|
||||
${getBackupsHTML()}
|
||||
</div>
|
||||
|
||||
<!-- Paramètres -->
|
||||
<div id="settings" class="content-section">
|
||||
${getSettingsHTML()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Charger les données
|
||||
loadDashboardData();
|
||||
}
|
||||
|
||||
// Changer d'onglet
|
||||
function switchTab(tabName) {
|
||||
// Masquer tous les onglets
|
||||
document.querySelectorAll('.content-section').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
|
||||
// Afficher l'onglet sélectionné
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
|
||||
// Mettre à jour les boutons
|
||||
document.querySelectorAll('.nav-tabs button').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
event.target.classList.add('active');
|
||||
|
||||
// Charger les données spécifiques
|
||||
if (tabName === 'console') loadConsoleData();
|
||||
if (tabName === 'logs') loadLogsData();
|
||||
if (tabName === 'players') loadPlayersData();
|
||||
if (tabName === 'whitelist') loadWhitelistData();
|
||||
if (tabName === 'backups') loadBackupsData();
|
||||
if (tabName === 'settings') loadSettingsData();
|
||||
}
|
||||
|
||||
// ========== DASHBOARD ==========
|
||||
function getDashboardHTML() {
|
||||
return `
|
||||
<div class="section-card">
|
||||
<h2>🎮 Contrôle du Serveur</h2>
|
||||
<div class="btn-group">
|
||||
<button class="btn-success" onclick="restartServer()">🔄 Redémarrer</button>
|
||||
<button class="btn-primary" onclick="saveServer()">💾 Sauvegarder</button>
|
||||
<button class="btn-warning" onclick="openChangeRconPasswordModal()">🔐 Changer RCON</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="grid-card">
|
||||
<h3>État Serveur</h3>
|
||||
<p class="value"><span class="status online"></span>En ligne</p>
|
||||
</div>
|
||||
<div class="grid-card">
|
||||
<h3>Joueurs Connectés</h3>
|
||||
<p class="value" id="playerCount">--</p>
|
||||
</div>
|
||||
<div class="grid-card">
|
||||
<h3>Version Forge</h3>
|
||||
<p class="value" id="forgeVersion">1.6.4</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<h2>📊 Informations Rapides</h2>
|
||||
<table>
|
||||
<tbody id="quickInfoTable">
|
||||
<tr><td>Chargement...</td><td></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadDashboardData() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/server`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.properties) {
|
||||
const table = document.getElementById('quickInfoTable');
|
||||
table.innerHTML = `
|
||||
<tr><td><strong>Nom Serveur</strong></td><td>${data.properties['motd'] || 'NationsGlory'}</td></tr>
|
||||
<tr><td><strong>Mode Jeu</strong></td><td>${data.properties['gamemode'] || 'Survival'}</td></tr>
|
||||
<tr><td><strong>Difficulté</strong></td><td>${data.properties['difficulty'] || '2'}</td></tr>
|
||||
<tr><td><strong>PvP</strong></td><td>${data.properties['pvp'] === 'true' ? 'Activé' : 'Désactivé'}</td></tr>
|
||||
<tr><td><strong>Port</strong></td><td>${data.properties['server-port'] || '25565'}</td></tr>
|
||||
<tr><td><strong>Max Joueurs</strong></td><td>${data.properties['max-players'] || '20'}</td></tr>
|
||||
`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur chargement dashboard:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== CONSOLE RCON ==========
|
||||
function getConsoleHTML() {
|
||||
return `
|
||||
<div class="section-card">
|
||||
<h2>⌨️ Console RCON</h2>
|
||||
<p style="margin-bottom: 15px; color: #666;">Exécutez des commandes directement sur le serveur</p>
|
||||
<div class="console-output" id="consoleOutput"></div>
|
||||
<div class="console-input">
|
||||
<input type="text" id="commandInput" placeholder="Entrez une commande..." onkeypress="if(event.key==='Enter') sendRconCommand();">
|
||||
<button class="btn-primary" onclick="sendRconCommand()">Envoyer</button>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 30px; margin-bottom: 15px;">Historique</h3>
|
||||
<div id="historyContainer" style="max-height: 300px; overflow-y: auto; background: #f8f9fa; border-radius: 5px; padding: 15px;"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadConsoleData() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/rcon/history`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
const history = await response.json();
|
||||
|
||||
const container = document.getElementById('historyContainer');
|
||||
container.innerHTML = history.map(h => `
|
||||
<div style="margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #ddd; font-size: 12px;">
|
||||
<strong style="color: var(--primary);">${h.command}</strong><br>
|
||||
<span style="color: #666;">${new Date(h.timestamp).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
`).join('') || '<p style="color: #666;">Aucun historique</p>';
|
||||
} catch (e) {
|
||||
console.error('Erreur historique:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendRconCommand() {
|
||||
const commandInput = document.getElementById('commandInput');
|
||||
const command = commandInput.value.trim();
|
||||
|
||||
if (!command) return;
|
||||
|
||||
const output = document.getElementById('consoleOutput');
|
||||
output.innerHTML += `<div>$ ${command}</div>`;
|
||||
commandInput.value = '';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/rcon/command`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ command })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
output.innerHTML += `<div>${data.response}</div>`;
|
||||
showMessage('Commande exécutée', 'success', 'dashboardMessage');
|
||||
} else {
|
||||
output.innerHTML += `<div class="error">Erreur: ${data.error}</div>`;
|
||||
showMessage(data.error, 'error', 'dashboardMessage');
|
||||
}
|
||||
|
||||
output.scrollTop = output.scrollHeight;
|
||||
} catch (e) {
|
||||
console.error('Erreur envoi commande:', e);
|
||||
output.innerHTML += `<div class="error">Erreur serveur</div>`;
|
||||
showMessage('Erreur serveur', 'error', 'dashboardMessage');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== LOGS ==========
|
||||
function getLogsHTML() {
|
||||
return `
|
||||
<div class="section-card">
|
||||
<h2>📜 Logs</h2>
|
||||
<div class="btn-group">
|
||||
<button class="btn-primary" onclick="reloadLogs()">🔄 Rafraîchir</button>
|
||||
<button class="btn-secondary" onclick="openSearchLogsModal()">🔍 Chercher</button>
|
||||
</div>
|
||||
<div class="logs-container" id="logsContainer"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadLogsData() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/logs?lines=200`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
const container = document.getElementById('logsContainer');
|
||||
container.innerHTML = data.logs.map((line, idx) => {
|
||||
let className = '';
|
||||
if (line.includes('ERROR')) className = 'error';
|
||||
else if (line.includes('WARN')) className = 'warning';
|
||||
|
||||
return `<div class="log-line ${className}">${escapeHtml(line)}</div>`;
|
||||
}).join('');
|
||||
|
||||
container.scrollTop = container.scrollHeight;
|
||||
} catch (e) {
|
||||
console.error('Erreur chargement logs:', e);
|
||||
document.getElementById('logsContainer').innerHTML = '<div class="error">Erreur chargement</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadLogs() {
|
||||
showMessage('Rechargement...', 'success', 'dashboardMessage');
|
||||
await loadLogsData();
|
||||
showMessage('Logs rechargés', 'success', 'dashboardMessage');
|
||||
}
|
||||
|
||||
function openSearchLogsModal() {
|
||||
const query = prompt('Chercher dans les logs:');
|
||||
if (query) searchLogs(query);
|
||||
}
|
||||
|
||||
async function searchLogs(query) {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/logs/search?query=${encodeURIComponent(query)}`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
const container = document.getElementById('logsContainer');
|
||||
container.innerHTML = data.results.map((r, idx) => `
|
||||
<div class="log-line" style="background: ${idx % 2 === 0 ? '#f8f9fa' : 'white'}; padding: 5px;">
|
||||
<strong style="color: var(--accent);">[${r.index}]</strong> ${escapeHtml(r.line)}
|
||||
</div>
|
||||
`).join('');
|
||||
} catch (e) {
|
||||
console.error('Erreur recherche:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== JOUEURS ==========
|
||||
function getPlayersHTML() {
|
||||
return `
|
||||
<div class="section-card">
|
||||
<h2>👥 Joueurs Connectés</h2>
|
||||
<p style="margin-bottom: 15px; color: #666;">Liste des joueurs qui se sont connectés</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>UUID</th>
|
||||
<th>Dernière Connexion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="playersTable">
|
||||
<tr><td colspan="3" style="text-align: center;">Chargement...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadPlayersData() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/players`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
const table = document.getElementById('playersTable');
|
||||
if (data.players.length > 0) {
|
||||
table.innerHTML = data.players.map(p => `
|
||||
<tr>
|
||||
<td>${p.name}</td>
|
||||
<td><code style="font-size: 11px;">${p.uuid}</code></td>
|
||||
<td>${new Date(p.lastPlayed).toLocaleString()}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
} else {
|
||||
table.innerHTML = '<tr><td colspan="3" style="text-align: center;">Aucun joueur</td></tr>';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur joueurs:', e);
|
||||
document.getElementById('playersTable').innerHTML = '<tr><td colspan="3" style="text-align: center; color: red;">Erreur</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
// ========== WHITELIST ==========
|
||||
function getWhitelistHTML() {
|
||||
return `
|
||||
<div class="section-card">
|
||||
<h2>✅ Whitelist</h2>
|
||||
<div class="btn-group">
|
||||
<button class="btn-primary" onclick="openAddWhitelistModal()">➕ Ajouter</button>
|
||||
<button class="btn-secondary" onclick="reloadWhitelist()">🔄 Rafraîchir</button>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Joueur</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="whitelistTable">
|
||||
<tr><td colspan="2" style="text-align: center;">Chargement...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadWhitelistData() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/whitelist`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
const table = document.getElementById('whitelistTable');
|
||||
if (data.whitelist && data.whitelist.length > 0) {
|
||||
const players = data.whitelist.map(w => typeof w === 'string' ? w : w.name);
|
||||
table.innerHTML = players.map(p => `
|
||||
<tr>
|
||||
<td>${p}</td>
|
||||
<td>
|
||||
<button class="btn-danger" style="padding: 5px 10px; font-size: 12px;" onclick="removeFromWhitelist('${p}')">❌ Retirer</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
} else {
|
||||
table.innerHTML = '<tr><td colspan="2" style="text-align: center;">Whitelist vide</td></tr>';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur whitelist:', e);
|
||||
document.getElementById('whitelistTable').innerHTML = '<tr><td colspan="2" style="text-align: center; color: red;">Erreur</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
function openAddWhitelistModal() {
|
||||
const username = prompt('Nom du joueur à ajouter:');
|
||||
if (username) addToWhitelist(username);
|
||||
}
|
||||
|
||||
async function addToWhitelist(username) {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/whitelist/add`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ username })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showMessage(`${username} ajouté à la whitelist`, 'success', 'dashboardMessage');
|
||||
loadWhitelistData();
|
||||
} else {
|
||||
showMessage(data.error, 'error', 'dashboardMessage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur:', e);
|
||||
showMessage('Erreur serveur', 'error', 'dashboardMessage');
|
||||
}
|
||||
}
|
||||
|
||||
async function removeFromWhitelist(username) {
|
||||
if (!confirm(`Confirmer la suppression de ${username}?`)) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/whitelist/remove`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ username })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showMessage(`${username} retiré de la whitelist`, 'success', 'dashboardMessage');
|
||||
loadWhitelistData();
|
||||
} else {
|
||||
showMessage(data.error, 'error', 'dashboardMessage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur:', e);
|
||||
showMessage('Erreur serveur', 'error', 'dashboardMessage');
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadWhitelist() {
|
||||
showMessage('Rechargement...', 'success', 'dashboardMessage');
|
||||
await loadWhitelistData();
|
||||
showMessage('Whitelist rechargée', 'success', 'dashboardMessage');
|
||||
}
|
||||
|
||||
// ========== BACKUPS ==========
|
||||
function getBackupsHTML() {
|
||||
return `
|
||||
<div class="section-card">
|
||||
<h2>💾 Backups</h2>
|
||||
<div class="btn-group">
|
||||
<button class="btn-success" onclick="createBackup()">➕ Créer Backup</button>
|
||||
<button class="btn-secondary" onclick="reloadBackups()">🔄 Rafraîchir</button>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Taille</th>
|
||||
<th>Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="backupsTable">
|
||||
<tr><td colspan="4" style="text-align: center;">Chargement...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadBackupsData() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/backup`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
const table = document.getElementById('backupsTable');
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
table.innerHTML = data.map(b => `
|
||||
<tr>
|
||||
<td>${b.name}</td>
|
||||
<td>${b.size}</td>
|
||||
<td>${new Date(b.created).toLocaleString()}</td>
|
||||
<td>
|
||||
<button class="btn-danger" style="padding: 5px 10px; font-size: 12px;" onclick="deleteBackup('${b.name}')">❌ Supprimer</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
} else {
|
||||
table.innerHTML = '<tr><td colspan="4" style="text-align: center;">Aucun backup</td></tr>';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur backups:', e);
|
||||
document.getElementById('backupsTable').innerHTML = '<tr><td colspan="4" style="text-align: center; color: red;">Erreur</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
async function createBackup() {
|
||||
if (!confirm('Créer un nouveau backup? (cela peut prendre du temps)')) return;
|
||||
|
||||
showMessage('Création du backup en cours...', 'success', 'dashboardMessage');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/backup/create`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showMessage(data.message, 'success', 'dashboardMessage');
|
||||
setTimeout(() => loadBackupsData(), 2000);
|
||||
} else {
|
||||
showMessage(data.error, 'error', 'dashboardMessage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur:', e);
|
||||
showMessage('Erreur serveur', 'error', 'dashboardMessage');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteBackup(filename) {
|
||||
if (!confirm(`Confirmer la suppression du backup ${filename}?`)) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/backup/delete/${filename}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showMessage('Backup supprimé', 'success', 'dashboardMessage');
|
||||
loadBackupsData();
|
||||
} else {
|
||||
showMessage(data.error, 'error', 'dashboardMessage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur:', e);
|
||||
showMessage('Erreur serveur', 'error', 'dashboardMessage');
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadBackups() {
|
||||
showMessage('Rechargement...', 'success', 'dashboardMessage');
|
||||
await loadBackupsData();
|
||||
showMessage('Backups rechargés', 'success', 'dashboardMessage');
|
||||
}
|
||||
|
||||
// ========== PARAMÈTRES ==========
|
||||
function getSettingsHTML() {
|
||||
return `
|
||||
<div class="section-card">
|
||||
<h2>⚙️ Paramètres du Serveur</h2>
|
||||
<table id="settingsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Paramètre</th>
|
||||
<th>Valeur</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td colspan="2" style="text-align: center;">Chargement...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadSettingsData() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/server`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
const table = document.getElementById('settingsTable');
|
||||
const tbody = table.querySelector('tbody');
|
||||
|
||||
if (data.properties) {
|
||||
const keys = Object.keys(data.properties).sort();
|
||||
tbody.innerHTML = keys.map(k => `
|
||||
<tr>
|
||||
<td><strong>${k}</strong></td>
|
||||
<td>${escapeHtml(data.properties[k])}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur paramètres:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== ACTIONS ==========
|
||||
|
||||
async function restartServer() {
|
||||
if (!confirm('⚠️ Redémarrer le serveur? Les joueurs seront déconnectés.')) return;
|
||||
|
||||
showMessage('Redémarrage du serveur...', 'success', 'dashboardMessage');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/rcon/restart`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showMessage(data.message, 'success', 'dashboardMessage');
|
||||
} else {
|
||||
showMessage(data.error, 'error', 'dashboardMessage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur:', e);
|
||||
showMessage('Erreur serveur', 'error', 'dashboardMessage');
|
||||
}
|
||||
}
|
||||
|
||||
async function saveServer() {
|
||||
showMessage('Sauvegarde du serveur...', 'success', 'dashboardMessage');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/rcon/save`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showMessage(data.message, 'success', 'dashboardMessage');
|
||||
} else {
|
||||
showMessage(data.error, 'error', 'dashboardMessage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur:', e);
|
||||
showMessage('Erreur serveur', 'error', 'dashboardMessage');
|
||||
}
|
||||
}
|
||||
|
||||
function openChangeRconPasswordModal() {
|
||||
const oldPassword = prompt('Ancien mot de passe RCON:');
|
||||
if (!oldPassword) return;
|
||||
|
||||
const newPassword = prompt('Nouveau mot de passe RCON:');
|
||||
if (!newPassword) return;
|
||||
|
||||
changeRconPassword(oldPassword, newPassword);
|
||||
}
|
||||
|
||||
async function changeRconPassword(oldPassword, newPassword) {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/auth/change-rcon-password`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ oldPassword, newPassword })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showMessage(data.message, 'success', 'dashboardMessage');
|
||||
} else {
|
||||
showMessage(data.error, 'error', 'dashboardMessage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur:', e);
|
||||
showMessage('Erreur serveur', 'error', 'dashboardMessage');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== UTILITAIRES ==========
|
||||
|
||||
function showMessage(text, type, elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
element.textContent = text;
|
||||
element.className = `message show ${type}`;
|
||||
setTimeout(() => {
|
||||
element.classList.remove('show');
|
||||
}, 4000);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
return text.replace(/[&<>"']/g, m => map[m]);
|
||||
}
|
||||
|
||||
// Déconnexion
|
||||
async function handleLogout() {
|
||||
try {
|
||||
await fetch(`${API_URL}/auth/logout`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
checkAuth();
|
||||
} catch (e) {
|
||||
console.error('Erreur:', e);
|
||||
}
|
||||
}
|
||||
41
install.sh
Executable file
41
install.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration pour l'interface web du serveur Minecraft NationsGlory
|
||||
# ====================================================================
|
||||
|
||||
echo "🚀 Installation de l'interface web NationsGlory Admin..."
|
||||
|
||||
# Vérifier que Node.js est installé
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo "❌ Node.js n'est pas installé. Veuillez l'installer d'abord."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Node.js détecté: $(node --version)"
|
||||
|
||||
# Créer le fichier .env
|
||||
if [ ! -f backend/.env ]; then
|
||||
echo "Création du fichier .env..."
|
||||
cp backend/.env.example backend/.env
|
||||
echo "✓ Fichier .env créé. À personnaliser dans backend/.env"
|
||||
fi
|
||||
|
||||
# Installer les dépendances du backend
|
||||
echo ""
|
||||
echo "📦 Installation des dépendances backend..."
|
||||
cd backend
|
||||
npm install
|
||||
cd ..
|
||||
|
||||
echo ""
|
||||
echo "✅ Installation terminée!"
|
||||
echo ""
|
||||
echo "📝 Prochaines étapes:"
|
||||
echo "1. Modifiez backend/.env avec la bonne configuration"
|
||||
echo "2. Lancez le serveur: cd backend && npm start"
|
||||
echo "3. Accédez à l'interface: http://localhost:3000"
|
||||
echo ""
|
||||
echo "ℹ️ IMPORTANT: Activez RCON dans le server.properties du serveur MC:"
|
||||
echo " - enable-rcon=true"
|
||||
echo " - rcon.port=25575"
|
||||
echo " - rcon.password=votre_mdp"
|
||||
21
setup.sh
Executable file
21
setup.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Rendre les scripts exécutables
|
||||
chmod +x install.sh
|
||||
chmod +x start.sh
|
||||
|
||||
# Créer les dossiers de data s'ils n'existent pas
|
||||
mkdir -p backend/data
|
||||
|
||||
echo "✅ Préparation complète!"
|
||||
echo ""
|
||||
echo "📖 Documentation:"
|
||||
echo " - README.md - Vue d'ensemble"
|
||||
echo " - CONFIGURATION.md - Guide de configuration détaillé"
|
||||
echo " - DEPLOYMENT.md - Déploiement en production"
|
||||
echo ""
|
||||
echo "🚀 Pour démarrer:"
|
||||
echo " 1. ./install.sh"
|
||||
echo " 2. Éditer backend/.env"
|
||||
echo " 3. ./start.sh"
|
||||
echo ""
|
||||
39
start.sh
Executable file
39
start.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script de démarrage pour le serveur et l'interface web
|
||||
# =========================================================
|
||||
|
||||
# Couleurs
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}🎮 NationsGlory Admin Panel - Démarrage${NC}"
|
||||
echo ""
|
||||
|
||||
# Vérifier que le backend existe
|
||||
if [ ! -d "backend" ]; then
|
||||
echo -e "${RED}❌ Dossier backend non trouvé!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Vérifier le fichier .env
|
||||
if [ ! -f "backend/.env" ]; then
|
||||
echo -e "${RED}❌ Fichier backend/.env non trouvé!${NC}"
|
||||
echo -e "${YELLOW}Copiez d'abord: cp backend/.env.example backend/.env${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Charger les variables d'environnement
|
||||
export $(cat backend/.env | grep -v '^#' | xargs)
|
||||
|
||||
echo -e "${GREEN}✓ Variables d'environnement chargées${NC}"
|
||||
echo " - Port: $PORT"
|
||||
echo " - Serveur MC: $SERVER_DIR"
|
||||
echo ""
|
||||
|
||||
# Démarrer le backend
|
||||
echo -e "${YELLOW}⏳ Démarrage du backend...${NC}"
|
||||
cd backend
|
||||
npm start
|
||||
Reference in New Issue
Block a user