269 lines
8.7 KiB
Python
269 lines
8.7 KiB
Python
#!/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)
|