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

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