- infrastructure.md: Network topology, server overview, credentials - gsm.md: Gameserver Monitor detailed documentation - todo.md: Project roadmap and completed tasks - CLAUDE.md: AI assistant context - temp/: Frontend component backups 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
197 lines
6.7 KiB
JavaScript
197 lines
6.7 KiB
JavaScript
import { useState, useEffect } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { getServers } from '../api'
|
|
import { useUser } from '../context/UserContext'
|
|
import ServerCard from '../components/ServerCard'
|
|
import SettingsModal from '../components/SettingsModal'
|
|
import UserManagement from '../components/UserManagement'
|
|
|
|
export default function Dashboard({ onLogout }) {
|
|
const navigate = useNavigate()
|
|
const { user, token, loading: userLoading, isSuperadmin, role } = useUser()
|
|
const [servers, setServers] = useState([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState('')
|
|
const [showSettings, setShowSettings] = useState(false)
|
|
const [showUserMgmt, setShowUserMgmt] = useState(false)
|
|
const [currentTime, setCurrentTime] = useState(new Date())
|
|
|
|
const fetchServers = async () => {
|
|
try {
|
|
const data = await getServers(token)
|
|
setServers(data)
|
|
setError('')
|
|
} catch (err) {
|
|
setError('CONNECTION_FAILED: Unable to reach server cluster')
|
|
if (err.message.includes('401') || err.message.includes('403')) {
|
|
onLogout()
|
|
}
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!userLoading) {
|
|
fetchServers()
|
|
const interval = setInterval(fetchServers, 10000)
|
|
return () => clearInterval(interval)
|
|
}
|
|
}, [token, userLoading])
|
|
|
|
useEffect(() => {
|
|
const timer = setInterval(() => setCurrentTime(new Date()), 1000)
|
|
return () => clearInterval(timer)
|
|
}, [])
|
|
|
|
const roleLabels = {
|
|
user: 'VIEWER',
|
|
moderator: 'OPERATOR',
|
|
superadmin: 'SYSADMIN'
|
|
}
|
|
|
|
if (userLoading) {
|
|
return (
|
|
<div className="min-h-screen bg-[#0d0d0d] flex items-center justify-center">
|
|
<div className="text-[#00ff41] glow-green text-xl">INITIALIZING SYSTEM...</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const onlineCount = servers.filter(s => s.running).length
|
|
const totalPlayers = servers.reduce((sum, s) => sum + (s.players?.online || 0), 0)
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#0d0d0d] hex-grid relative overflow-x-hidden">
|
|
{/* Matrix rain overlay */}
|
|
<div className="matrix-bg" />
|
|
|
|
{/* Header */}
|
|
<header className="relative z-10 border-b border-[#00ff41]/30 bg-black/80 backdrop-blur-sm">
|
|
<div className="container-main py-4">
|
|
<div className="flex justify-between items-center">
|
|
<div className="flex items-center gap-6">
|
|
<h1 className="text-xl md:text-2xl font-bold text-[#00ff41] glow-green font-mono">
|
|
GAMESERVER_MONITOR
|
|
</h1>
|
|
<div className="hidden md:flex items-center gap-4 text-[#00ff41]/60 text-sm font-mono">
|
|
<span className="flex items-center gap-2">
|
|
<span className="w-2 h-2 rounded-full status-online"></span>
|
|
{onlineCount}/{servers.length} NODES
|
|
</span>
|
|
<span>|</span>
|
|
<span>{totalPlayers} USERS_CONNECTED</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
<div className="hidden sm:block text-[#00ff41]/60 font-mono text-sm">
|
|
{currentTime.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<div className="text-right hidden sm:block">
|
|
<div className="text-[#00ff41] font-mono text-sm">{user?.username?.toUpperCase()}</div>
|
|
<div className="text-[#00ff41]/50 text-xs">[{roleLabels[role]}]</div>
|
|
</div>
|
|
|
|
{isSuperadmin && (
|
|
<button
|
|
onClick={() => setShowUserMgmt(true)}
|
|
className="btn-matrix px-3 py-1.5 text-sm font-mono"
|
|
>
|
|
USERS
|
|
</button>
|
|
)}
|
|
|
|
<button
|
|
onClick={() => setShowSettings(true)}
|
|
className="btn-matrix px-3 py-1.5 text-sm font-mono"
|
|
>
|
|
SETTINGS
|
|
</button>
|
|
|
|
<button
|
|
onClick={onLogout}
|
|
className="btn-matrix px-3 py-1.5 text-sm font-mono"
|
|
>
|
|
DISCONNECT
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main className="relative z-10 container-main py-8">
|
|
{/* System Status Bar */}
|
|
<div className="mb-8 p-4 border border-[#00ff41]/30 bg-black/60 backdrop-blur-sm rounded font-mono text-sm">
|
|
<div className="flex items-center gap-2 text-[#00ff41]/80">
|
|
<span className="text-[#00ff41]">></span>
|
|
<span>SYSTEM_STATUS: {error ? 'ERROR' : 'OPERATIONAL'}</span>
|
|
</div>
|
|
{error && (
|
|
<div className="mt-2 text-red-500 flex items-center gap-2">
|
|
<span className="animate-pulse">!</span>
|
|
{error}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="text-center py-12">
|
|
<div className="text-[#00ff41] glow-green text-xl animate-pulse">
|
|
LOADING_NODES...
|
|
</div>
|
|
<div className="mt-4 flex justify-center gap-1">
|
|
{[...Array(5)].map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className="w-3 h-8 bg-[#00ff41]/30"
|
|
style={{
|
|
animation: `pulse 1s ease-in-out ${i * 0.1}s infinite`
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{servers.map((server, index) => (
|
|
<div
|
|
key={server.id}
|
|
className="opacity-0 fade-in-up"
|
|
style={{ animationDelay: `${index * 0.1}s` }}
|
|
>
|
|
<ServerCard
|
|
server={server}
|
|
onClick={() => navigate(`/server/${server.id}`)}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Footer Stats */}
|
|
<div className="mt-12 pt-6 border-t border-[#00ff41]/20">
|
|
<div className="flex flex-wrap justify-center gap-6 text-[#00ff41]/50 font-mono text-xs">
|
|
<span>REFRESH_RATE: 10s</span>
|
|
<span>PROTOCOL: RCON/SSH</span>
|
|
<span>METRICS: PROMETHEUS</span>
|
|
<span>BUILD: v2.0.0-matrix</span>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
{/* Modals */}
|
|
{showSettings && (
|
|
<SettingsModal onClose={() => setShowSettings(false)} />
|
|
)}
|
|
|
|
{showUserMgmt && (
|
|
<UserManagement onClose={() => setShowUserMgmt(false)} />
|
|
)}
|
|
</div>
|
|
)
|
|
}
|