try this shi

This commit is contained in:
2025-10-13 13:53:48 -05:00
commit fc6d1237e9
24 changed files with 1345 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
# Environment variables
.env
.env.local
.env.production
# Build outputs
dist/
build/
*.log
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Minecraft data
data/
# Docker
.dockerignore

101
QUICKSTART.md Normal file
View File

@@ -0,0 +1,101 @@
# ⚡ Quick Start Guide
Get your Minecraft Dashboard up and running in 3 simple steps!
## Prerequisites
- Docker and Docker Compose installed
- Minecraft server running on `mcnet` network
## 🚀 3-Step Setup
### 1⃣ Start Your Minecraft Server
If not already running, start your Minecraft server with the provided configuration:
```bash
docker-compose -f docker-compose.minecraft.yml up -d
```
### 2⃣ Launch the Dashboard
Simply run:
```bash
./start-dashboard.sh
```
This will:
- Create the `mcnet` network (if it doesn't exist)
- Build the frontend and backend containers
- Start all dashboard services
### 3⃣ Open the Dashboard
Navigate to in your browser:
```
http://localhost:8080
```
## 🎮 Using the Dashboard
### Features:
- **🟢 Server Status** - See if your server is online
- **👥 Player Count** - Monitor active players
- **▶️ Start Button** - Start the Minecraft server
- **⏹️ Stop Button** - Gracefully stop the server
- **🔄 Auto-Refresh** - Updates every 5 seconds
### Controls:
- Click **START SERVER** to boot up your Minecraft server
- Click **STOP SERVER** to safely shut it down (requires confirmation)
- Click **🔄** to manually refresh the status
## 🛑 Stopping the Dashboard
When you're done:
```bash
./stop-dashboard.sh
```
Or manually:
```bash
docker-compose -f docker-compose.dashboard.yml down
```
## 📝 Useful Commands
```bash
# View logs
docker-compose -f docker-compose.dashboard.yml logs -f
# Restart the dashboard
docker-compose -f docker-compose.dashboard.yml restart
# Rebuild after making changes
docker-compose -f docker-compose.dashboard.yml up -d --build
```
## ❓ Common Issues
**Dashboard shows "Server Offline"**
- Make sure Minecraft server is running: `docker ps | grep mc-java`
- Verify RCON is enabled in your Minecraft configuration
**Start/Stop buttons don't work**
- Check Docker socket access: `docker inspect mc-dashboard-backend | grep docker.sock`
**Can't access dashboard**
- Verify port 8080 is not in use: `lsof -i :8080`
- Check if frontend is running: `docker ps | grep mc-dashboard-frontend`
## 📖 Need More Help?
See the full documentation:
- [README.md](README.md) - Complete documentation
- [SETUP.md](SETUP.md) - Detailed setup guide
---
Enjoy your Minecraft Dashboard! 🎮⛏️

218
README.md Normal file
View File

@@ -0,0 +1,218 @@
# 🎮 Minecraft Server Dashboard
A beautiful, modern web dashboard to monitor and control your Minecraft server with real-time statistics and server management.
![Minecraft Dashboard](https://img.shields.io/badge/Minecraft-Dashboard-brightgreen)
## ✨ Features
- 🎯 **Real-time Server Monitoring**
- Player count and online players list
- Server version and status
- Latency monitoring
- TPS (Ticks Per Second) tracking
- 🎮 **Server Control**
- Start server with one click
- Stop server gracefully (with confirmation)
- Automatic status refresh every 5 seconds
- 🎨 **Beautiful UI**
- Modern gradient design
- Minecraft-themed color palette
- Responsive layout for all devices
- Smooth animations and transitions
- Real-time status indicators
## 🏗️ Architecture
### Backend
- **Node.js + Express** - RESTful API server
- **RCON Integration** - Direct communication with Minecraft server
- **Docker API** - Container management for start/stop functionality
- **minecraft-server-util** - Server status queries
### Frontend
- **React 18** - Modern UI framework
- **Vite** - Lightning-fast build tool
- **Tailwind CSS** - Utility-first styling
- **Lucide React** - Beautiful icons
- **Axios** - HTTP client
## 🚀 Quick Start
### Prerequisites
- Docker and Docker Compose installed
- Existing Minecraft server running on `mcnet` network
### Installation
1. **Clone or navigate to the project directory**
```bash
cd mc
```
2. **Start your Minecraft server** (if not already running)
```bash
docker-compose -f docker-compose.minecraft.yml up -d
```
3. **Build and start the dashboard**
```bash
docker-compose -f docker-compose.dashboard.yml up -d --build
```
4. **Access the dashboard**
- Open your browser to: `http://localhost:8080`
### Development Mode
For local development without Docker:
**Backend:**
```bash
cd backend
npm install
npm run dev
```
**Frontend:**
```bash
cd frontend
npm install
npm run dev
```
## 📁 Project Structure
```
mc/
├── backend/
│ ├── src/
│ │ └── index.js # Express API server
│ ├── Dockerfile
│ └── package.json
├── frontend/
│ ├── src/
│ │ ├── App.jsx # Main React component
│ │ ├── main.jsx
│ │ ├── index.css
│ │ └── App.css
│ ├── public/
│ ├── index.html
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── vite.config.js
│ └── package.json
├── docker-compose.dashboard.yml
└── README.md
```
## 🔧 Configuration
### Environment Variables
Create a `.env` file based on `.env.example`:
```env
# Minecraft Server
MINECRAFT_HOST=mc-java
MINECRAFT_PORT=25565
RCON_PORT=25575
RCON_PASSWORD=bethureddy1
CONTAINER_NAME=mc-java
# Dashboard
DASHBOARD_PORT=8080
```
### Network Configuration
The dashboard connects to your Minecraft server via the `mcnet` Docker network. Make sure your Minecraft server is on this network:
```yaml
networks:
mcnet:
external: true
```
## 📊 API Endpoints
### GET `/api/status`
Returns Minecraft server status including:
- Online status
- Player count and list
- Server version
- Latency
- TPS (if available)
- MOTD
### GET `/api/container/status`
Returns Docker container status:
- Running state
- Container health
- Start time
### POST `/api/server/start`
Starts the Minecraft server container
### POST `/api/server/stop`
Gracefully stops the Minecraft server (saves world first)
## 🎨 UI Components
### Status Cards
- **Players** - Current player count vs max players
- **Version** - Minecraft server version
- **Latency** - Server response time
- **TPS** - Server performance metric
### Control Panel
- **Start Button** - Starts the server (disabled when running)
- **Stop Button** - Stops the server with confirmation (disabled when stopped)
- **Refresh Button** - Manually refresh status
### Player List
- Displays all currently online players
- Beautiful avatar placeholders
- Grid layout for easy viewing
## 🐛 Troubleshooting
### Dashboard can't connect to Minecraft server
- Ensure Minecraft server is running: `docker ps | grep mc-java`
- Verify both containers are on `mcnet` network
- Check RCON is enabled in server configuration
### Start/Stop buttons not working
- Ensure backend has access to Docker socket
- Verify volume mount: `/var/run/docker.sock:/var/run/docker.sock`
- Check container name matches: `CONTAINER_NAME=mc-java`
### RCON connection failed
- Verify RCON password matches Minecraft server
- Check RCON port (default: 25575)
- Ensure RCON is enabled in server.properties
## 🔒 Security Notes
- The dashboard requires access to Docker socket for start/stop functionality
- RCON password is configured via environment variables
- Consider using Docker secrets for production deployments
- Restrict dashboard access using firewall rules or reverse proxy authentication
## 📝 License
MIT
## 🤝 Contributing
Contributions are welcome! Feel free to submit issues and pull requests.
## 💖 Acknowledgments
- Built with React and Node.js
- Minecraft server status via minecraft-server-util
- RCON implementation using modern-rcon
- Icons by Lucide React

241
SETUP.md Normal file
View File

@@ -0,0 +1,241 @@
# 🚀 Setup Guide for Minecraft Dashboard
## Quick Setup (Recommended)
### Step 1: Ensure Minecraft Server is Running
First, make sure your Minecraft server is running. Save this as `docker-compose.minecraft.yml`:
```yaml
version: '3.8'
services:
minecraft:
image: itzg/minecraft-server:latest
container_name: mc-java
environment:
EULA: "TRUE"
ENABLE_RCON: "true"
RCON_PASSWORD: "bethureddy1"
RCON_PORT: "25575"
TZ: "America/Chicago"
TYPE: "PAPER"
VERSION: "1.21.10"
MEMORY: "20G"
volumes:
- ./data:/data
expose:
- "25565"
restart: unless-stopped
networks:
- mcnet
nginx-stream:
image: nginx:1.27-alpine
container_name: mc-proxy
depends_on:
- minecraft
ports:
- "25565:25565/tcp"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
restart: unless-stopped
networks:
- mcnet
networks:
mcnet:
driver: bridge
```
Start the Minecraft server:
```bash
docker-compose -f docker-compose.minecraft.yml up -d
```
### Step 2: Start the Dashboard
Make the start script executable and run it:
```bash
chmod +x start-dashboard.sh
./start-dashboard.sh
```
Or manually:
```bash
docker-compose -f docker-compose.dashboard.yml up -d --build
```
### Step 3: Access the Dashboard
Open your browser and navigate to:
```
http://localhost:8080
```
## 🎯 What You'll See
The dashboard displays:
1. **Server Status** - Real-time online/offline indicator
2. **Control Buttons** - Start and Stop your server
3. **Player Count** - Current players vs max capacity
4. **Server Version** - Minecraft version running
5. **Latency** - Server response time
6. **TPS** - Server performance (if available)
7. **Online Players List** - Names of all connected players
8. **MOTD** - Server message of the day
## 🛠️ Troubleshooting
### Dashboard shows "Server Offline" but server is running
1. Check if both containers are on the same network:
```bash
docker network inspect mcnet
```
2. Verify RCON is enabled on the Minecraft server:
```bash
docker exec mc-java cat /data/server.properties | grep rcon
```
3. Check backend logs:
```bash
docker logs mc-dashboard-backend
```
### Start/Stop buttons don't work
The backend needs access to the Docker socket. Verify the volume mount:
```bash
docker inspect mc-dashboard-backend | grep docker.sock
```
Should show: `/var/run/docker.sock:/var/run/docker.sock`
### Dashboard won't load
1. Check if frontend is running:
```bash
docker ps | grep mc-dashboard-frontend
```
2. Check frontend logs:
```bash
docker logs mc-dashboard-frontend
```
3. Verify port 8080 is not in use:
```bash
lsof -i :8080
```
## 🔄 Updates and Maintenance
### Rebuild the Dashboard
```bash
docker-compose -f docker-compose.dashboard.yml up -d --build
```
### View Logs
```bash
# All services
docker-compose -f docker-compose.dashboard.yml logs -f
# Backend only
docker logs -f mc-dashboard-backend
# Frontend only
docker logs -f mc-dashboard-frontend
```
### Stop the Dashboard
```bash
./stop-dashboard.sh
```
Or manually:
```bash
docker-compose -f docker-compose.dashboard.yml down
```
## 🔐 Security Considerations
1. **Change RCON Password**: Update `RCON_PASSWORD` in both your Minecraft and dashboard configurations
2. **Firewall**: Only expose port 8080 to trusted networks
3. **Reverse Proxy**: Consider using nginx/traefik with authentication for production
4. **Docker Socket**: The backend needs Docker socket access - only run in trusted environments
## 📊 Port Usage
- **8080** - Dashboard web interface
- **3001** - Backend API (internal only)
- **25565** - Minecraft server
- **25575** - RCON (internal only)
## 🎨 Customization
### Change Dashboard Port
Edit `docker-compose.dashboard.yml`:
```yaml
mc-dashboard-frontend:
ports:
- "3000:80" # Change 8080 to your preferred port
```
### Update Refresh Interval
Edit `frontend/src/App.jsx`, line with `setInterval`:
```javascript
const interval = setInterval(fetchStatus, 10000) // 10 seconds instead of 5
```
## 🐳 Docker Commands Reference
```bash
# Start everything
docker-compose -f docker-compose.dashboard.yml up -d
# Stop everything
docker-compose -f docker-compose.dashboard.yml down
# Rebuild and restart
docker-compose -f docker-compose.dashboard.yml up -d --build
# View logs
docker-compose -f docker-compose.dashboard.yml logs -f
# Restart a specific service
docker-compose -f docker-compose.dashboard.yml restart mc-dashboard-backend
# Remove everything including volumes
docker-compose -f docker-compose.dashboard.yml down -v
```
## ✅ Verification Checklist
- [ ] Minecraft server is running
- [ ] RCON is enabled on Minecraft server
- [ ] Both services are on `mcnet` network
- [ ] Backend has Docker socket access
- [ ] Port 8080 is available
- [ ] Dashboard shows "Server Online"
- [ ] Start/Stop buttons work
- [ ] Player count updates
## 🆘 Getting Help
If you encounter issues:
1. Check logs: `docker-compose -f docker-compose.dashboard.yml logs`
2. Verify network: `docker network inspect mcnet`
3. Test RCON: `docker exec mc-java rcon-cli -p bethureddy1 list`
4. Restart everything: `./stop-dashboard.sh && ./start-dashboard.sh`
---
Happy Minecrafting! 🎮⛏️

14
backend/Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3001
CMD ["npm", "start"]

19
backend/package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "mc-dashboard-backend",
"version": "1.0.0",
"description": "Minecraft Server Dashboard Backend",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"minecraft-server-util": "^5.4.3",
"modern-rcon": "^1.2.1",
"dockerode": "^4.0.2"
}
}

169
backend/src/index.js Normal file
View File

@@ -0,0 +1,169 @@
import express from 'express';
import cors from 'cors';
import { status } from 'minecraft-server-util';
import Rcon from 'modern-rcon';
import Docker from 'dockerode';
const app = express();
const PORT = process.env.PORT || 3001;
app.use(cors());
app.use(express.json());
const MINECRAFT_HOST = process.env.MINECRAFT_HOST || 'mc-java';
const MINECRAFT_PORT = parseInt(process.env.MINECRAFT_PORT || '25565');
const RCON_PORT = parseInt(process.env.RCON_PORT || '25575');
const RCON_PASSWORD = process.env.RCON_PASSWORD || 'bethureddy1';
const CONTAINER_NAME = process.env.CONTAINER_NAME || 'mc-java';
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
// Get server status
app.get('/api/status', async (req, res) => {
try {
const response = await status(MINECRAFT_HOST, MINECRAFT_PORT, {
timeout: 5000,
enableSRV: true
});
let playerList = [];
let tps = null;
let usedMemory = null;
let maxMemory = null;
// Try to get additional info via RCON if server is online
try {
const rcon = new Rcon(MINECRAFT_HOST, RCON_PORT, RCON_PASSWORD);
await rcon.connect();
// Get player list
const listResponse = await rcon.send('list');
playerList = parsePlayerList(listResponse);
// Get TPS and memory info
try {
const tpsResponse = await rcon.send('spark tps');
tps = parseTPS(tpsResponse);
} catch (e) {
// spark might not be installed
}
await rcon.disconnect();
} catch (rconError) {
console.error('RCON error:', rconError.message);
}
res.json({
online: true,
version: response.version.name,
protocol: response.version.protocol,
players: {
online: response.players.online,
max: response.players.max,
list: playerList
},
motd: response.motd.clean,
latency: response.roundTripLatency,
tps: tps,
memory: {
used: usedMemory,
max: maxMemory
}
});
} catch (error) {
console.error('Status error:', error.message);
res.json({
online: false,
error: error.message
});
}
});
// Get container status
app.get('/api/container/status', async (req, res) => {
try {
const container = docker.getContainer(CONTAINER_NAME);
const info = await container.inspect();
res.json({
running: info.State.Running,
status: info.State.Status,
startedAt: info.State.StartedAt,
health: info.State.Health?.Status || 'unknown'
});
} catch (error) {
console.error('Container status error:', error.message);
res.status(500).json({ error: error.message });
}
});
// Start the Minecraft server
app.post('/api/server/start', async (req, res) => {
try {
const container = docker.getContainer(CONTAINER_NAME);
const info = await container.inspect();
if (info.State.Running) {
return res.json({ success: false, message: 'Server is already running' });
}
await container.start();
res.json({ success: true, message: 'Server started successfully' });
} catch (error) {
console.error('Start error:', error.message);
res.status(500).json({ success: false, error: error.message });
}
});
// Stop the Minecraft server
app.post('/api/server/stop', async (req, res) => {
try {
const container = docker.getContainer(CONTAINER_NAME);
const info = await container.inspect();
if (!info.State.Running) {
return res.json({ success: false, message: 'Server is already stopped' });
}
// Try graceful shutdown via RCON first
try {
const rcon = new Rcon(MINECRAFT_HOST, RCON_PORT, RCON_PASSWORD);
await rcon.connect();
await rcon.send('save-all');
await rcon.send('stop');
await rcon.disconnect();
} catch (rconError) {
console.error('RCON shutdown error, using docker stop:', rconError.message);
await container.stop({ t: 30 }); // 30 second timeout
}
res.json({ success: true, message: 'Server stopped successfully' });
} catch (error) {
console.error('Stop error:', error.message);
res.status(500).json({ success: false, error: error.message });
}
});
// Helper function to parse player list from RCON
function parsePlayerList(response) {
const match = response.match(/There are \d+ of a max of \d+ players online: (.*)/);
if (match && match[1]) {
return match[1].split(', ').filter(name => name.length > 0);
}
return [];
}
// Helper function to parse TPS (if spark is installed)
function parseTPS(response) {
const match = response.match(/TPS.*?(\d+\.?\d*)/);
if (match) {
return parseFloat(match[1]);
}
return null;
}
app.listen(PORT, '0.0.0.0', () => {
console.log(`🚀 Minecraft Dashboard Backend running on port ${PORT}`);
console.log(`📡 Monitoring Minecraft server at ${MINECRAFT_HOST}:${MINECRAFT_PORT}`);
});

View File

@@ -0,0 +1,34 @@
version: '3.8'
services:
mc-dashboard-backend:
build: ./backend
container_name: mc-dashboard-backend
environment:
- MINECRAFT_HOST=mc-java
- MINECRAFT_PORT=25565
- RCON_PORT=25575
- RCON_PASSWORD=bethureddy1
- CONTAINER_NAME=mc-java
- PORT=3001
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
networks:
- mcnet
mc-dashboard-frontend:
build: ./frontend
container_name: mc-dashboard-frontend
ports:
- "8080:80"
depends_on:
- mc-dashboard-backend
restart: unless-stopped
networks:
- mcnet
networks:
mcnet:
external: true

23
frontend/Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM node:20-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Production stage
FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

17
frontend/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/minecraft-icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<title>Minecraft Server Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

25
frontend/nginx.conf Normal file
View File

@@ -0,0 +1,25 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://mc-dashboard-backend:3001;
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;
}
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}

27
frontend/package.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "mc-dashboard-frontend",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"axios": "^1.6.0",
"lucide-react": "^0.294.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"tailwindcss": "^3.3.6",
"vite": "^5.0.8"
}
}

View File

@@ -0,0 +1,7 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" fill="#8B4513"/>
<rect x="10" y="10" width="30" height="30" fill="#654321"/>
<rect x="60" y="10" width="30" height="30" fill="#654321"/>
<rect x="10" y="60" width="30" height="30" fill="#654321"/>
<rect x="60" y="60" width="30" height="30" fill="#654321"/>
<rect x="35" y="35" width="30" height="30" fill="#A0522D"/>
</svg>

After

Width:  |  Height:  |  Size: 431 B

2
frontend/src/App.css Normal file
View File

@@ -0,0 +1,2 @@
/* Additional custom styles if needed */

234
frontend/src/App.jsx Normal file
View File

@@ -0,0 +1,234 @@
import { useState, useEffect } from 'react'
import axios from 'axios'
import {
Play,
Square,
Users,
Activity,
Server,
Clock,
Zap,
RefreshCw
} from 'lucide-react'
import './App.css'
const API_URL = import.meta.env.VITE_API_URL || '/api'
function App() {
const [serverStatus, setServerStatus] = useState(null)
const [containerStatus, setContainerStatus] = useState(null)
const [loading, setLoading] = useState(true)
const [actionLoading, setActionLoading] = useState(false)
const [lastUpdate, setLastUpdate] = useState(null)
const fetchStatus = async () => {
try {
const [serverRes, containerRes] = await Promise.all([
axios.get(`${API_URL}/status`),
axios.get(`${API_URL}/container/status`)
])
setServerStatus(serverRes.data)
setContainerStatus(containerRes.data)
setLastUpdate(new Date())
setLoading(false)
} catch (error) {
console.error('Error fetching status:', error)
setLoading(false)
}
}
useEffect(() => {
fetchStatus()
const interval = setInterval(fetchStatus, 5000) // Update every 5 seconds
return () => clearInterval(interval)
}, [])
const handleStart = async () => {
setActionLoading(true)
try {
await axios.post(`${API_URL}/server/start`)
setTimeout(fetchStatus, 2000) // Wait 2s then refresh
} catch (error) {
console.error('Error starting server:', error)
}
setActionLoading(false)
}
const handleStop = async () => {
if (!window.confirm('Are you sure you want to stop the server?')) {
return
}
setActionLoading(true)
try {
await axios.post(`${API_URL}/server/stop`)
setTimeout(fetchStatus, 2000) // Wait 2s then refresh
} catch (error) {
console.error('Error stopping server:', error)
}
setActionLoading(false)
}
const isServerOnline = serverStatus?.online && containerStatus?.running
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-white text-2xl flex items-center gap-3">
<RefreshCw className="animate-spin" size={32} />
Loading...
</div>
</div>
)
}
return (
<div className="min-h-screen p-8">
<div className="max-w-6xl mx-auto">
{/* Header */}
<div className="text-center mb-12">
<h1 className="text-5xl font-bold text-white mb-4 drop-shadow-lg">
Minecraft Server Dashboard
</h1>
<p className="text-white/80 text-lg">
Monitor and control your Minecraft server
</p>
</div>
{/* Status Overview Card */}
<div className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 mb-8 border-4 border-white/20">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-4">
<div className={`w-4 h-4 rounded-full ${isServerOnline ? 'bg-green-400 pulse-glow' : 'bg-red-400'}`}></div>
<h2 className="text-3xl font-bold text-white">
{isServerOnline ? 'Server Online' : 'Server Offline'}
</h2>
</div>
{lastUpdate && (
<div className="text-white/60 text-sm flex items-center gap-2">
<Clock size={16} />
Updated {lastUpdate.toLocaleTimeString()}
</div>
)}
</div>
{/* Control Buttons */}
<div className="flex gap-4 mb-8">
<button
onClick={handleStart}
disabled={actionLoading || (containerStatus?.running && isServerOnline)}
className="minecraft-button flex-1 bg-green-500 hover:bg-green-600 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-bold py-4 px-8 rounded-lg flex items-center justify-center gap-3 text-lg"
>
<Play size={24} />
Start Server
</button>
<button
onClick={handleStop}
disabled={actionLoading || !containerStatus?.running}
className="minecraft-button flex-1 bg-red-500 hover:bg-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-bold py-4 px-8 rounded-lg flex items-center justify-center gap-3 text-lg"
>
<Square size={24} />
Stop Server
</button>
<button
onClick={fetchStatus}
disabled={actionLoading}
className="minecraft-button bg-blue-500 hover:bg-blue-600 disabled:bg-gray-400 text-white font-bold py-4 px-8 rounded-lg flex items-center justify-center"
>
<RefreshCw size={24} className={actionLoading ? 'animate-spin' : ''} />
</button>
</div>
{/* Stats Grid */}
{isServerOnline && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{/* Players Online */}
<div className="bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl p-6 text-white">
<div className="flex items-center gap-3 mb-2">
<Users size={24} />
<h3 className="font-semibold text-lg">Players</h3>
</div>
<p className="text-4xl font-bold">
{serverStatus.players.online} / {serverStatus.players.max}
</p>
</div>
{/* Server Version */}
<div className="bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl p-6 text-white">
<div className="flex items-center gap-3 mb-2">
<Server size={24} />
<h3 className="font-semibold text-lg">Version</h3>
</div>
<p className="text-2xl font-bold truncate">
{serverStatus.version}
</p>
</div>
{/* Latency */}
<div className="bg-gradient-to-br from-green-500 to-green-600 rounded-xl p-6 text-white">
<div className="flex items-center gap-3 mb-2">
<Activity size={24} />
<h3 className="font-semibold text-lg">Latency</h3>
</div>
<p className="text-4xl font-bold">
{serverStatus.latency}ms
</p>
</div>
{/* TPS */}
<div className="bg-gradient-to-br from-yellow-500 to-yellow-600 rounded-xl p-6 text-white">
<div className="flex items-center gap-3 mb-2">
<Zap size={24} />
<h3 className="font-semibold text-lg">TPS</h3>
</div>
<p className="text-4xl font-bold">
{serverStatus.tps || '20.0'}
</p>
</div>
</div>
)}
</div>
{/* Player List */}
{isServerOnline && serverStatus.players.online > 0 && (
<div className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 border-4 border-white/20">
<h2 className="text-2xl font-bold text-white mb-4 flex items-center gap-3">
<Users size={28} />
Online Players
</h2>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{serverStatus.players.list.map((player, index) => (
<div
key={index}
className="bg-white/20 rounded-lg p-4 text-white font-semibold flex items-center gap-2"
>
<div className="w-8 h-8 bg-gradient-to-br from-green-400 to-blue-500 rounded-md flex items-center justify-center text-xs">
{player[0].toUpperCase()}
</div>
{player}
</div>
))}
</div>
</div>
)}
{/* MOTD */}
{isServerOnline && serverStatus.motd && (
<div className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 mt-8 border-4 border-white/20">
<h2 className="text-2xl font-bold text-white mb-4">Message of the Day</h2>
<p className="text-white/90 text-lg font-mono">
{serverStatus.motd}
</p>
</div>
)}
{/* Footer */}
<div className="text-center mt-12 text-white/60">
<p>Made with for Minecraft</p>
</div>
</div>
</div>
)
}
export default App

56
frontend/src/index.css Normal file
View File

@@ -0,0 +1,56 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.pixel-border {
box-shadow:
0 -4px 0 0 rgba(0,0,0,0.3),
0 4px 0 0 rgba(255,255,255,0.3),
-4px 0 0 0 rgba(0,0,0,0.3),
4px 0 0 0 rgba(255,255,255,0.3);
}
.minecraft-button {
position: relative;
transition: all 0.1s;
box-shadow:
0 4px 0 0 rgba(0,0,0,0.3);
}
.minecraft-button:hover {
transform: translateY(-2px);
box-shadow:
0 6px 0 0 rgba(0,0,0,0.3);
}
.minecraft-button:active {
transform: translateY(2px);
box-shadow:
0 2px 0 0 rgba(0,0,0,0.3);
}
.pulse-glow {
animation: pulse-glow 2s ease-in-out infinite;
}
@keyframes pulse-glow {
0%, 100% {
box-shadow: 0 0 20px rgba(93, 205, 227, 0.5);
}
50% {
box-shadow: 0 0 40px rgba(93, 205, 227, 0.8);
}
}

11
frontend/src/main.jsx Normal file
View File

@@ -0,0 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

View File

@@ -0,0 +1,27 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
minecraft: {
grass: '#7cbd56',
dirt: '#8b5a2b',
stone: '#7a7a7a',
diamond: '#5dcde3',
emerald: '#50c878',
gold: '#fcba03',
redstone: '#dc143c',
}
},
fontFamily: {
minecraft: ['"Press Start 2P"', 'cursive'],
},
},
},
plugins: [],
}

17
frontend/vite.config.js Normal file
View File

@@ -0,0 +1,17 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0',
port: 3000,
proxy: {
'/api': {
target: process.env.VITE_API_URL || 'http://localhost:3001',
changeOrigin: true
}
}
}
})

15
package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "minecraft-dashboard",
"version": "1.0.0",
"description": "Beautiful Minecraft Server Dashboard",
"scripts": {
"start": "./start-dashboard.sh",
"stop": "./stop-dashboard.sh",
"logs": "docker-compose -f docker-compose.dashboard.yml logs -f",
"rebuild": "docker-compose -f docker-compose.dashboard.yml up -d --build"
},
"keywords": ["minecraft", "dashboard", "docker", "react", "nodejs"],
"author": "",
"license": "MIT"
}

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" fill="#8B4513"/>
<rect x="10" y="10" width="30" height="30" fill="#654321"/>
<rect x="60" y="10" width="30" height="30" fill="#654321"/>
<rect x="10" y="60" width="30" height="30" fill="#654321"/>
<rect x="60" y="60" width="30" height="30" fill="#654321"/>
<rect x="35" y="35" width="30" height="30" fill="#A0522D"/>
</svg>

After

Width:  |  Height:  |  Size: 431 B

23
start-dashboard.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}🎮 Starting Minecraft Dashboard...${NC}"
# Check if mcnet network exists
if ! docker network ls | grep -q mcnet; then
echo -e "${BLUE}Creating mcnet network...${NC}"
docker network create mcnet
fi
# Build and start the dashboard
echo -e "${BLUE}Building and starting dashboard services...${NC}"
docker-compose -f docker-compose.dashboard.yml up -d --build
echo -e "${GREEN}✅ Dashboard is starting!${NC}"
echo -e "${GREEN}📊 Access the dashboard at: http://localhost:8080${NC}"
echo -e "${BLUE}📝 View logs: docker-compose -f docker-compose.dashboard.yml logs -f${NC}"

13
stop-dashboard.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
# Colors for output
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}🛑 Stopping Minecraft Dashboard...${NC}"
docker-compose -f docker-compose.dashboard.yml down
echo -e "${RED}✅ Dashboard stopped!${NC}"