398 lines
17 KiB
Python
398 lines
17 KiB
Python
import json
|
|
import time
|
|
import requests
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
from collections import deque
|
|
|
|
import Settings
|
|
from Multiplayer_Server import LoadData, SaveData
|
|
|
|
MAX_HISTORY_LENGTH = 100 # Store last 100 data points
|
|
|
|
class WebUIHandler(BaseHTTPRequestHandler):
|
|
player_count_history = deque(maxlen=MAX_HISTORY_LENGTH)
|
|
|
|
def do_GET(self):
|
|
if self.path == '/':
|
|
self.send_response(200)
|
|
self.send_header('Content-type', 'text/html')
|
|
self.end_headers()
|
|
self.wfile.write(self.generate_html().encode())
|
|
elif self.path == '/data':
|
|
self.send_response(200)
|
|
self.send_header('Content-type', 'application/json')
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps(self.get_server_data()).encode())
|
|
else:
|
|
self.send_error(404)
|
|
|
|
def generate_html(self):
|
|
html = """
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Dani's Race - Retro Dashboard</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
|
|
|
|
:root {
|
|
--bg-color: #000000;
|
|
--text-color: #00ff00;
|
|
--accent-color: #ff00ff;
|
|
--secondary-accent: #00ffff;
|
|
--alert-color: #ff0000;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Press Start 2P', cursive;
|
|
margin: 0;
|
|
padding: 0;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-color);
|
|
overflow-x: hidden;
|
|
line-height: 1.6;
|
|
}
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
h1, h2, h3 {
|
|
color: var(--accent-color);
|
|
text-shadow: 2px 2px var(--bg-color);
|
|
}
|
|
.dashboard-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.card {
|
|
border: 2px solid var(--text-color);
|
|
padding: 20px;
|
|
background-color: rgba(0, 255, 0, 0.1);
|
|
box-shadow: 0 0 10px var(--text-color);
|
|
}
|
|
.stat {
|
|
font-size: 2em;
|
|
color: var(--secondary-accent);
|
|
margin: 10px 0;
|
|
text-shadow: 2px 2px var(--bg-color);
|
|
}
|
|
#server-status {
|
|
padding: 10px;
|
|
border: 2px solid var(--accent-color);
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
text-transform: uppercase;
|
|
animation: glow 1s ease-in-out infinite alternate;
|
|
}
|
|
@keyframes glow {
|
|
from {
|
|
box-shadow: 0 0 5px var(--accent-color);
|
|
}
|
|
to {
|
|
box-shadow: 0 0 20px var(--accent-color);
|
|
}
|
|
}
|
|
.online {
|
|
background-color: rgba(0, 255, 0, 0.2);
|
|
}
|
|
.offline {
|
|
background-color: rgba(255, 0, 0, 0.2);
|
|
}
|
|
#log-container {
|
|
border: 2px solid var(--text-color);
|
|
padding: 20px;
|
|
height: 200px;
|
|
overflow-y: auto;
|
|
background-color: rgba(0, 255, 0, 0.1);
|
|
font-size: 0.8em;
|
|
}
|
|
.log-entry {
|
|
margin-bottom: 5px;
|
|
font-family: monospace;
|
|
}
|
|
#player-count-chart {
|
|
width: 100%;
|
|
height: 300px;
|
|
margin-top: 20px;
|
|
}
|
|
@keyframes blink {
|
|
0% { opacity: 1; }
|
|
50% { opacity: 0; }
|
|
100% { opacity: 1; }
|
|
}
|
|
.blink {
|
|
animation: blink 1s infinite;
|
|
}
|
|
.connection-info {
|
|
margin-top: 20px;
|
|
padding: 10px;
|
|
background-color: var(--accent-color);
|
|
color: var(--bg-color);
|
|
text-align: center;
|
|
font-weight: bold;
|
|
border: 2px solid var(--text-color);
|
|
box-shadow: 0 0 10px var(--accent-color);
|
|
}
|
|
#alerts {
|
|
color: var(--alert-color);
|
|
font-weight: bold;
|
|
margin-top: 20px;
|
|
padding: 10px;
|
|
border: 2px solid var(--alert-color);
|
|
display: none;
|
|
}
|
|
.room-list {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 10px;
|
|
}
|
|
.room-item {
|
|
background-color: rgba(0, 255, 255, 0.1);
|
|
padding: 10px;
|
|
border: 1px solid var(--secondary-accent);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1 class="blink">Dani's Race Dashboard</h1>
|
|
<div class="connection-info">
|
|
Connect to the game server at: https://dani.madiator.com:6969
|
|
</div>
|
|
<div id="server-status">Server Status: <span id="status-text">Checking...</span></div>
|
|
<div id="alerts"></div>
|
|
<div class="dashboard-grid">
|
|
<div class="card">
|
|
<h3>Active Users</h3>
|
|
<div id="active-users" class="stat">-</div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>Total Objects</h3>
|
|
<div id="total-objects" class="stat">-</div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>Total Rooms</h3>
|
|
<div id="total-rooms" class="stat">-</div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>Server Uptime</h3>
|
|
<div id="server-uptime" class="stat">-</div>
|
|
</div>
|
|
</div>
|
|
<h2>Player Count History</h2>
|
|
<canvas id="player-count-chart"></canvas>
|
|
<h2>Active Rooms:</h2>
|
|
<div id="rooms" class="room-list"></div>
|
|
<h2>Server Log:</h2>
|
|
<div id="log-container"></div>
|
|
</div>
|
|
<script>
|
|
let startTime = Date.now();
|
|
let playerCountChart;
|
|
let lastServerTimestamp = 0;
|
|
|
|
function updateDashboard() {
|
|
fetch('/data')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.getElementById('active-users').textContent = data.active_users;
|
|
document.getElementById('total-objects').textContent = data.total_objects;
|
|
document.getElementById('total-rooms').textContent = Object.keys(data.rooms).length;
|
|
|
|
const uptime = Math.floor((Date.now() - startTime) / 1000);
|
|
document.getElementById('server-uptime').textContent = formatUptime(uptime);
|
|
|
|
const roomsContainer = document.getElementById('rooms');
|
|
roomsContainer.innerHTML = '';
|
|
|
|
for (const [room, users] of Object.entries(data.rooms)) {
|
|
const roomDiv = document.createElement('div');
|
|
roomDiv.className = 'room-item';
|
|
roomDiv.innerHTML = `
|
|
<h3>${room}</h3>
|
|
<div>Users: ${users.length}</div>
|
|
<ul>
|
|
${users.map(user => `<li>${user}</li>`).join('')}
|
|
</ul>
|
|
`;
|
|
roomsContainer.appendChild(roomDiv);
|
|
}
|
|
|
|
updateLog(data.log);
|
|
updatePlayerCountChart(data.player_count_history);
|
|
|
|
checkServerRestart(data.server_timestamp);
|
|
checkRoomCleanups(data.log);
|
|
|
|
document.getElementById('status-text').textContent = data.server_status;
|
|
document.getElementById('server-status').className = data.server_status.toLowerCase();
|
|
|
|
if (data.server_status === 'Offline') {
|
|
showAlert('Game server is offline. It will automatically restart. Please stand by.');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
document.getElementById('status-text').textContent = 'Offline';
|
|
document.getElementById('server-status').className = 'offline';
|
|
showAlert('Web UI server is offline. Please refresh the page.');
|
|
});
|
|
}
|
|
|
|
function formatUptime(seconds) {
|
|
const hours = Math.floor(seconds / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
const secs = seconds % 60;
|
|
return `${hours}h ${minutes}m ${secs}s`;
|
|
}
|
|
|
|
function updateLog(logEntries) {
|
|
const logContainer = document.getElementById('log-container');
|
|
logContainer.innerHTML = '';
|
|
logEntries.forEach(entry => {
|
|
const logEntry = document.createElement('div');
|
|
logEntry.className = 'log-entry';
|
|
logEntry.textContent = entry;
|
|
logContainer.appendChild(logEntry);
|
|
});
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
}
|
|
|
|
function updatePlayerCountChart(history) {
|
|
const ctx = document.getElementById('player-count-chart').getContext('2d');
|
|
|
|
if (!playerCountChart) {
|
|
playerCountChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: history.map((_, index) => index),
|
|
datasets: [{
|
|
label: 'Player Count',
|
|
data: history,
|
|
borderColor: '#00ffff',
|
|
backgroundColor: 'rgba(0, 255, 255, 0.2)',
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
title: {
|
|
display: true,
|
|
text: 'Number of Players',
|
|
color: '#00ff00'
|
|
},
|
|
ticks: {
|
|
color: '#00ff00'
|
|
},
|
|
grid: {
|
|
color: 'rgba(0, 255, 0, 0.1)'
|
|
}
|
|
},
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: 'Time',
|
|
color: '#00ff00'
|
|
},
|
|
ticks: {
|
|
color: '#00ff00'
|
|
},
|
|
grid: {
|
|
color: 'rgba(0, 255, 0, 0.1)'
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
color: '#00ff00'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
playerCountChart.data.labels = history.map((_, index) => index);
|
|
playerCountChart.data.datasets[0].data = history;
|
|
playerCountChart.update();
|
|
}
|
|
}
|
|
|
|
function checkServerRestart(serverTimestamp) {
|
|
if (lastServerTimestamp && serverTimestamp < lastServerTimestamp) {
|
|
showAlert('Server has restarted. Please refresh your game client.');
|
|
}
|
|
lastServerTimestamp = serverTimestamp;
|
|
}
|
|
|
|
function checkRoomCleanups(logEntries) {
|
|
const cleanupEntries = logEntries.filter(entry => entry.includes('Room') && entry.includes('cleaned up'));
|
|
if (cleanupEntries.length > 0) {
|
|
showAlert('Inactive rooms have been cleaned up. Check the log for details.');
|
|
}
|
|
}
|
|
|
|
function showAlert(message) {
|
|
const alertsContainer = document.getElementById('alerts');
|
|
alertsContainer.textContent = message;
|
|
alertsContainer.style.display = 'block';
|
|
setTimeout(() => {
|
|
alertsContainer.style.display = 'none';
|
|
}, 5000);
|
|
}
|
|
|
|
setInterval(updateDashboard, 1000);
|
|
updateDashboard();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
return html
|
|
|
|
def get_server_data(self):
|
|
try:
|
|
# Try to get health status from the game server
|
|
health_response = requests.get("http://localhost:6971/health", timeout=5)
|
|
server_status = "Online" if health_response.status_code == 200 else "Offline"
|
|
except requests.RequestException:
|
|
server_status = "Offline"
|
|
|
|
users = LoadData("users", {})
|
|
objects = LoadData("objects", {})
|
|
rooms = {}
|
|
for user_id, user_data in users.items():
|
|
room = user_data.get("room", "Unknown")
|
|
if room not in rooms:
|
|
rooms[room] = []
|
|
rooms[room].append(user_data.get("username", f"User-{user_id[:5]}"))
|
|
|
|
# Get the last 20 log entries
|
|
log_entries = LoadData("server_log", [])[-20:]
|
|
|
|
# Update player count history
|
|
self.player_count_history.append(len(users))
|
|
|
|
return {
|
|
"server_status": server_status,
|
|
"active_users": len(users),
|
|
"total_objects": len(objects),
|
|
"rooms": rooms,
|
|
"log": log_entries,
|
|
"player_count_history": list(self.player_count_history),
|
|
"server_timestamp": time.time()
|
|
}
|
|
|
|
if __name__ == "__main__":
|
|
PORT = 6970
|
|
serve = HTTPServer(("", PORT), WebUIHandler)
|
|
print(f"Web UI server running on port {PORT}")
|
|
serve.serve_forever()
|