Upload files to "/"
This commit is contained in:
parent
11053ae87a
commit
371d61ad1d
2 changed files with 763 additions and 0 deletions
365
Multiplayer_Server.py
Normal file
365
Multiplayer_Server.py
Normal file
|
@ -0,0 +1,365 @@
|
|||
import os
|
||||
import zlib
|
||||
import json
|
||||
import time
|
||||
import threading
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from collections import deque
|
||||
|
||||
import Settings
|
||||
from Multiplayer_Shared import *
|
||||
|
||||
MAX_HISTORY_LENGTH = 100 # Store last 100 data points
|
||||
|
||||
class GameServerHandler(BaseHTTPRequestHandler):
|
||||
player_count_history = deque(maxlen=MAX_HISTORY_LENGTH)
|
||||
|
||||
def do_POST(self):
|
||||
data = self.Recieve()
|
||||
response = ParseRequest(data)
|
||||
self.Send(response)
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/health':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.end_headers()
|
||||
self.wfile.write(b"OK")
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def Send(self, data):
|
||||
# Compressing
|
||||
data = json.dumps(data)
|
||||
data = data.encode("utf-8")
|
||||
data = zlib.compress(data)
|
||||
|
||||
# Sending
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type","application/zip")
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def Recieve(self):
|
||||
length = int(self.headers.get('content-length'))
|
||||
data = self.rfile.read(length)
|
||||
data = zlib.decompress(data)
|
||||
data = json.loads(data)
|
||||
return data
|
||||
|
||||
def log_message(self, format, *args):
|
||||
log_entry = f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {format%args}"
|
||||
log_entries = LoadData("server_log", [])
|
||||
log_entries.append(log_entry)
|
||||
if len(log_entries) > 100: # Keep only the last 100 entries
|
||||
log_entries = log_entries[-100:]
|
||||
SaveData("server_log", log_entries)
|
||||
|
||||
def ParseRequest(data):
|
||||
resp = {}
|
||||
|
||||
for key in data:
|
||||
payload = data[key]
|
||||
|
||||
if "login" == key:
|
||||
resp[key] = Login(payload)
|
||||
elif "change-ownership" == key:
|
||||
resp[key] = ChangeOwnership(payload)
|
||||
elif "scene" == key:
|
||||
resp[key] = Scene(data)
|
||||
elif "vision" == key:
|
||||
pass
|
||||
else:
|
||||
print(key, payload)
|
||||
|
||||
userId = data.get("login", {}).get("userId")
|
||||
room = Safe(data.get("login", {}).get("room"))
|
||||
messages = LoadData("messages", {}, room)
|
||||
if messages.get(userId):
|
||||
resp["message"] = messages[userId].pop(0)
|
||||
SaveData("messages", messages, room)
|
||||
|
||||
return resp
|
||||
|
||||
def Login(data):
|
||||
resp = {}
|
||||
newuser = False
|
||||
|
||||
# Checking userId
|
||||
if not data.get("userId"):
|
||||
resp["userId"] = RandomString()
|
||||
newuser = True
|
||||
|
||||
# Checking room
|
||||
if not data.get("room"):
|
||||
resp["room"] = "MainRoom"
|
||||
elif data.get("room") != Safe(data.get("room", "")):
|
||||
resp["room"] = Safe(data["room"])
|
||||
|
||||
data["timestamp"] = time.time()
|
||||
|
||||
# Storing users data.
|
||||
users = LoadData("users", {})
|
||||
|
||||
if data.get("userId"):
|
||||
users[data["userId"]] = data
|
||||
|
||||
SaveData("users", users)
|
||||
|
||||
if newuser:
|
||||
userId = resp.get("userId")
|
||||
name = data.get("username")
|
||||
room = Safe(data.get("room"))
|
||||
NotifyOthers(userId, f"{name} joined game.", room)
|
||||
log_user_joined(name, room)
|
||||
|
||||
return resp
|
||||
|
||||
def ChangeOwnership(data):
|
||||
objects = LoadData("objects", {})
|
||||
if data.get("netId") in objects:
|
||||
obj = objects[data.get("netId")]
|
||||
obj["ownerId"] = data.get("userId")
|
||||
|
||||
SaveData("objects", objects)
|
||||
|
||||
# Changing ownership in chunk
|
||||
addr = obj.get("chunk")
|
||||
room = Safe(obj.get("room"))
|
||||
chunk = LoadData(addr, {}, room)
|
||||
for o in chunk:
|
||||
if o.get("netId") == data.get("netId"):
|
||||
o["ownerId"] = data.get("userId")
|
||||
SaveData(addr, chunk, room)
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def Scene(data):
|
||||
scene = data["scene"]
|
||||
userId = data.get("login", {}).get("userId")
|
||||
room = Safe(data.get("login", {}).get("room", "MainRoom"))
|
||||
|
||||
# We are not updating scene if there is no userId yet.
|
||||
if not userId:
|
||||
return
|
||||
|
||||
# Reading scene payload.
|
||||
resp = {}
|
||||
objects = LoadData("objects", {})
|
||||
|
||||
for addr in scene:
|
||||
chunk = scene[addr]
|
||||
|
||||
for obj in chunk:
|
||||
# Some people might want to cheat by inputing
|
||||
# values for other users.
|
||||
if obj.get("netId"):
|
||||
obj["ownerId"] = objects.get(obj.get("netId"), {}).get("ownerId")
|
||||
if not obj["ownerId"]:
|
||||
obj["ownerId"] = userId
|
||||
|
||||
if obj.get("ownerId") and obj.get("ownerId") != userId:
|
||||
chunk.remove(obj)
|
||||
|
||||
# Saving chunks data.
|
||||
for addr in scene:
|
||||
chunk = scene[addr]
|
||||
saved = LoadData(addr, [], room)
|
||||
add = []
|
||||
|
||||
# Cleaning up
|
||||
for obj in saved:
|
||||
if obj.get("netId") not in objects or objects[obj.get("netId")].get("chunk") != addr:
|
||||
saved.remove(obj)
|
||||
|
||||
ids = []
|
||||
for obj in saved:
|
||||
ids.append(obj.get("netId"))
|
||||
|
||||
for obj in chunk:
|
||||
# If object is Dani and it has no netId
|
||||
# we assume that somebody new joined the
|
||||
# room.
|
||||
if obj.get("name") == "Dani_Box" and not obj.get("netId"):
|
||||
obj["netId"] = userId
|
||||
obj["ownerId"] = userId
|
||||
add.append(obj)
|
||||
elif not obj.get("netId"):
|
||||
obj["netId"] = RandomString()
|
||||
obj["ownerId"] = userId
|
||||
add.append(obj)
|
||||
|
||||
if obj.get("netId") not in ids:
|
||||
saved.append(obj)
|
||||
else:
|
||||
saved[ids.index(obj.get("netId"))] = obj
|
||||
|
||||
objects[obj["netId"]] = {"chunk":addr,
|
||||
"timestamp":time.time(),
|
||||
"ownerId":obj.get("ownerId"),
|
||||
"room":room}
|
||||
|
||||
SaveData(addr, saved, room)
|
||||
|
||||
if addr in data.get("vision", []):
|
||||
resp[addr] = []
|
||||
for obj in saved:
|
||||
if obj.get("ownerId") != userId:
|
||||
resp[addr].append(obj)
|
||||
for obj in add:
|
||||
resp[addr].append(obj)
|
||||
|
||||
SaveData("objects", objects)
|
||||
|
||||
return resp
|
||||
|
||||
def Notify(userId, message, room):
|
||||
messages = LoadData("messages", {}, room)
|
||||
if userId not in messages:
|
||||
messages[userId] = []
|
||||
messages[userId].append(message)
|
||||
SaveData("messages", messages, room)
|
||||
|
||||
def NotifyOthers(userId, message, room):
|
||||
users = LoadData("users", {})
|
||||
for user in users:
|
||||
if user != userId and users[user].get("room") == room:
|
||||
Notify(user, message, room)
|
||||
|
||||
print(f"Room: {room} : {message}")
|
||||
|
||||
def SaveData(name, data, room=None):
|
||||
# Making folder for server stuff
|
||||
folder = Settings.get_settings_folder()+"/server/"
|
||||
if room: folder = folder+str(room)+"/"
|
||||
try: os.makedirs(folder)
|
||||
except: pass
|
||||
|
||||
# Saving the json
|
||||
with open(folder+str(name)+".json", "w") as save:
|
||||
json.dump(data, save, indent=4)
|
||||
|
||||
def LoadData(name, otherwise=None, room=None):
|
||||
try:
|
||||
folder = Settings.get_settings_folder()+"/server/"
|
||||
if room: folder = folder+str(room)+"/"
|
||||
with open(folder+str(name)+".json") as o:
|
||||
return json.load(o)
|
||||
except: return otherwise
|
||||
|
||||
def Safe(string):
|
||||
# This function strips strings from any unsafe
|
||||
# characters.
|
||||
good = "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_"
|
||||
new = ""
|
||||
for i in string:
|
||||
if i in good: new = new + i
|
||||
else: new = new + "_"
|
||||
|
||||
return new
|
||||
|
||||
def log_user_joined(name, room):
|
||||
log_entry = f"User '{name}' joined room '{room}'."
|
||||
log_entries = LoadData("server_log", [])
|
||||
log_entries.append(log_entry)
|
||||
if len(log_entries) > 100:
|
||||
log_entries = log_entries[-100:]
|
||||
SaveData("server_log", log_entries)
|
||||
|
||||
def log_user_left(name, room):
|
||||
log_entry = f"User '{name}' left room '{room}'."
|
||||
log_entries = LoadData("server_log", [])
|
||||
log_entries.append(log_entry)
|
||||
if len(log_entries) > 100:
|
||||
log_entries = log_entries[-100:]
|
||||
SaveData("server_log", log_entries)
|
||||
|
||||
def cleanup_rooms():
|
||||
base_dir = Path(Settings.get_settings_folder()) / "server"
|
||||
users = LoadData("users", {})
|
||||
|
||||
for item in base_dir.iterdir():
|
||||
if item.is_dir():
|
||||
room_name = item.name
|
||||
# Check if any user is in this room
|
||||
users_in_room = [user for user in users.values() if user.get("room") == room_name]
|
||||
if not users_in_room:
|
||||
print(f"Cleaning up room: {room_name}")
|
||||
shutil.rmtree(item)
|
||||
log_cleanup(room_name)
|
||||
|
||||
def log_cleanup(room_name):
|
||||
log_entry = f"Room '{room_name}' has been cleaned up due to inactivity."
|
||||
log_entries = LoadData("server_log", [])
|
||||
log_entries.append(log_entry)
|
||||
if len(log_entries) > 100:
|
||||
log_entries = log_entries[-100:]
|
||||
SaveData("server_log", log_entries)
|
||||
|
||||
def CleanUps():
|
||||
while True:
|
||||
time.sleep(30) # Run every 30 seconds
|
||||
|
||||
# Cleaning up users
|
||||
users = LoadData("users", {})
|
||||
delete = []
|
||||
for userId in users:
|
||||
user = users[userId]
|
||||
room = user.get("room")
|
||||
name = user.get("username")
|
||||
|
||||
# If user missing for 10 seconds, they are gone.
|
||||
if user.get("timestamp", 0) < time.time() - 10:
|
||||
NotifyOthers(userId, f"{name} left the game.", room)
|
||||
delete.append(userId)
|
||||
log_user_left(name, room)
|
||||
|
||||
for i in delete:
|
||||
del users[i]
|
||||
|
||||
SaveData("users", users)
|
||||
|
||||
# Cleaning up objects
|
||||
objects = LoadData("objects", {})
|
||||
delete = []
|
||||
for netId in objects:
|
||||
obj = objects[netId]
|
||||
|
||||
# Deleting objects after 5 missing seconds
|
||||
if obj.get("timestamp", 0) < time.time() - 5:
|
||||
delete.append(netId)
|
||||
|
||||
for i in delete:
|
||||
del objects[i]
|
||||
|
||||
SaveData("objects", objects)
|
||||
|
||||
# Cleaning up empty rooms
|
||||
cleanup_rooms()
|
||||
|
||||
def health_check_thread():
|
||||
health_port = 6971
|
||||
health_server = HTTPServer(("", health_port), GameServerHandler)
|
||||
print(f"Health check server running on port {health_port}")
|
||||
health_server.serve_forever()
|
||||
|
||||
if __name__ == "__main__":
|
||||
PORT = 6969
|
||||
serve = HTTPServer(("", PORT), GameServerHandler)
|
||||
print(f"Game server running on port {PORT}")
|
||||
|
||||
# Start the cleanup thread
|
||||
cleanups = threading.Thread(target=CleanUps)
|
||||
cleanups.setDaemon(True)
|
||||
cleanups.start()
|
||||
|
||||
# Start the health check thread
|
||||
health_thread = threading.Thread(target=health_check_thread)
|
||||
health_thread.setDaemon(True)
|
||||
health_thread.start()
|
||||
|
||||
serve.serve_forever()
|
398
web-ui.py
Normal file
398
web-ui.py
Normal file
|
@ -0,0 +1,398 @@
|
|||
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()
|
Loading…
Reference in a new issue