Warhummer/monitor.py
2025-06-25 09:35:26 +08:00

269 lines
8.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
from flask import Flask, render_template_string, jsonify
import sqlite3
import datetime
import os
app = Flask(__name__)
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>战锤40K行星总督 - 游戏监控</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #0a0a0a;
color: #e0e0e0;
background-image: linear-gradient(to bottom, #0a0a0a, #1a1a1a);
margin: 0;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: rgba(30, 30, 35, 0.9);
border: 2px solid #600;
border-radius: 10px;
padding: 20px;
box-shadow: 0 0 20px rgba(153, 0, 0, 0.5);
}
h1, h2 {
color: #d4af37;
text-shadow: 0 0 10px rgba(212, 175, 55, 0.5);
text-align: center;
}
.stats {
display: flex;
justify-content: space-around;
margin: 30px 0;
flex-wrap: wrap;
}
.stat-card {
background-color: rgba(0, 0, 0, 0.5);
border: 1px solid #600;
border-radius: 8px;
padding: 20px;
width: 200px;
margin: 10px;
text-align: center;
}
.stat-value {
font-size: 2.5rem;
font-weight: bold;
color: #d4af37;
margin: 10px 0;
}
.stat-label {
font-size: 1rem;
color: #999;
}
.user-list {
background-color: rgba(0, 0, 0, 0.5);
border: 1px solid #444;
border-radius: 8px;
padding: 15px;
margin-top: 20px;
max-height: 300px;
overflow-y: auto;
}
.user-item {
display: flex;
justify-content: space-between;
padding: 8px;
border-bottom: 1px solid #333;
}
.user-item:last-child {
border-bottom: none;
}
.playing {
color: #4caf50;
}
.idle {
color: #ff9800;
}
.refresh-time {
text-align: center;
margin-top: 20px;
color: #999;
font-size: 0.8rem;
}
.btn {
background-color: #600;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
display: block;
margin: 20px auto;
font-weight: bold;
}
.btn:hover {
background-color: #900;
}
</style>
</head>
<body>
<div class="container">
<h1>战锤40K行星总督</h1>
<h2>服务器监控面板</h2>
<div class="stats">
<div class="stat-card">
<div class="stat-label">总在线用户</div>
<div class="stat-value" id="total-users">{{ total_users }}</div>
</div>
<div class="stat-card">
<div class="stat-label">游戏中用户</div>
<div class="stat-value" id="playing-users">{{ playing_users }}</div>
</div>
<div class="stat-card">
<div class="stat-label">注册用户总数</div>
<div class="stat-value">{{ registered_users }}</div>
</div>
</div>
<div class="user-list">
<h3>当前在线用户:</h3>
{% if active_users %}
{% for user in active_users %}
<div class="user-item">
<span>{{ user.username }}</span>
<span class="{{ user.status }}">{{ user.status }}</span>
</div>
{% endfor %}
{% else %}
<div class="user-item">当前没有用户在线</div>
{% endif %}
</div>
<button class="btn" onclick="refreshData()">刷新数据</button>
<div class="refresh-time">上次更新时间: {{ refresh_time }}</div>
</div>
<script>
function refreshData() {
fetch('/monitor/api/stats')
.then(response => response.json())
.then(data => {
document.getElementById('total-users').textContent = data.total_users;
document.getElementById('playing-users').textContent = data.playing_users;
// 更新刷新时间
const refreshTimeElement = document.querySelector('.refresh-time');
refreshTimeElement.textContent = '上次更新时间: ' + data.refresh_time;
// 自动刷新整个页面以更新用户列表
setTimeout(() => {
window.location.reload();
}, 100);
})
.catch(error => console.error('获取数据失败:', error));
}
// 每60秒自动刷新一次
setInterval(refreshData, 60000);
</script>
</body>
</html>
"""
def get_db_connection():
"""连接到SQLite数据库"""
db_path = os.path.join('data', 'warhammer.db')
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
return conn
def get_stats():
"""获取游戏统计数据"""
try:
conn = get_db_connection()
# 获取注册用户总数
registered_users = conn.execute('SELECT COUNT(*) as count FROM users').fetchone()['count']
# 获取活跃会话 (假设数据库中有sessions表根据实际情况调整)
# 如果没有sessions表需要创建或使用其他方式跟踪在线用户
try:
cursor = conn.execute('''
SELECT s.*, u.username
FROM sessions s
JOIN users u ON s.user_id = u.id
WHERE s.last_activity > ?
''', (datetime.datetime.now() - datetime.timedelta(minutes=30),))
active_sessions = cursor.fetchall()
except sqlite3.OperationalError:
# 如果sessions表不存在尝试备选方案
try:
# 尝试从game_states表获取活跃用户
cursor = conn.execute('''
SELECT gs.*, u.username, u.id as user_id
FROM game_states gs
JOIN users u ON gs.user_id = u.id
WHERE gs.updated_at > ?
''', ((datetime.datetime.now() - datetime.timedelta(minutes=30)).isoformat(),))
active_sessions = cursor.fetchall()
# 添加状态字段模拟
for session in active_sessions:
# 假设最近30分钟内更新的都是在玩游戏的
session_dict = dict(session)
session_dict['status'] = 'playing'
session = session_dict
except:
# 备选方案也失败,返回空列表
active_sessions = []
# 计算游戏中的用户数
playing_users = sum(1 for session in active_sessions if session['status'] == 'playing')
# 格式化活跃用户列表
active_users = []
for session in active_sessions:
user_info = {
'username': session['username'],
'status': session['status'],
'user_id': session['user_id']
}
active_users.append(user_info)
conn.close()
return {
'registered_users': registered_users,
'total_users': len(active_sessions),
'playing_users': playing_users,
'active_users': active_users,
'refresh_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
except Exception as e:
print(f"获取统计数据时出错: {str(e)}")
return {
'registered_users': 0,
'total_users': 0,
'playing_users': 0,
'active_users': [],
'refresh_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'error': str(e)
}
@app.route('/monitor')
def monitor_page():
"""监控页面"""
stats = get_stats()
return render_template_string(HTML_TEMPLATE, **stats)
@app.route('/monitor/api/stats')
def api_stats():
"""统计数据API"""
return jsonify(get_stats())
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5050, debug=False)