LichessStatTgWeb/LichessWebView/templates/index.html
vrubelroman c256d903f2 Добавлено отображение количества игроков в веб-интерфейсе
- API теперь возвращает total_gamers (общее количество уникальных игроков)
- В интерфейсе по порту 5000 отображается: Всего пользователей и Кол-во игроков
2025-10-28 22:20:49 +03:00

445 lines
14 KiB
HTML
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.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lichess Bot Users Monitor</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
display: grid;
grid-template-columns: 400px 1fr;
gap: 20px;
height: calc(100vh - 40px);
}
.panel {
background: white;
border-radius: 15px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
overflow-y: auto;
}
.users-panel {
display: flex;
flex-direction: column;
}
.gamers-panel {
display: flex;
flex-direction: column;
}
h1 {
color: #333;
margin-bottom: 20px;
font-size: 24px;
}
.stats {
background: #f8f9fa;
padding: 15px;
border-radius: 10px;
margin-bottom: 15px;
font-size: 14px;
color: #666;
}
.stats strong {
color: #667eea;
font-size: 20px;
}
.search-box {
margin-bottom: 15px;
}
.search-box input {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s;
}
.search-box input:focus {
outline: none;
border-color: #667eea;
}
.users-list {
flex: 1;
}
.user-item {
padding: 15px;
margin-bottom: 10px;
background: #f8f9fa;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
border: 2px solid transparent;
}
.user-item:hover {
background: #e9ecef;
transform: translateX(5px);
}
.user-item.active {
background: #667eea;
color: white;
border-color: #5568d3;
}
.user-item.active .user-stats {
color: rgba(255, 255, 255, 0.8);
}
.user-name {
font-weight: bold;
font-size: 16px;
margin-bottom: 5px;
}
.user-info {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.user-item.active .user-info {
color: rgba(255, 255, 255, 0.7);
}
.user-stats {
font-size: 12px;
color: #667eea;
display: flex;
gap: 15px;
margin-top: 8px;
}
.user-item.active .user-stats {
color: rgba(255, 255, 255, 0.9);
}
.user-stats span {
padding: 3px 8px;
background: white;
border-radius: 5px;
}
.user-item.active .user-stats span {
background: rgba(255, 255, 255, 0.2);
}
.gamers-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.gamers-table th {
background: #f8f9fa;
padding: 12px;
text-align: left;
font-weight: 600;
color: #333;
border-bottom: 2px solid #dee2e6;
}
.gamers-table td {
padding: 12px;
border-bottom: 1px solid #dee2e6;
}
.gamers-table tr:hover {
background: #f8f9fa;
}
.badge {
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.badge-success {
background: #d4edda;
color: #155724;
}
.badge-secondary {
background: #e2e3e5;
color: #383d41;
}
.badge-token {
background: #fff3cd;
color: #856404;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.empty {
text-align: center;
padding: 40px;
color: #999;
}
.selected-user-info {
background: #f8f9fa;
padding: 15px;
border-radius: 10px;
margin-bottom: 15px;
}
.selected-user-info h2 {
color: #333;
font-size: 18px;
margin-bottom: 10px;
}
.selected-user-info p {
color: #666;
font-size: 14px;
margin: 5px 0;
}
</style>
</head>
<body>
<div class="container">
<!-- Левая панель: Пользователи -->
<div class="panel users-panel">
<h1>👥 Пользователи</h1>
<div class="stats">
Всего пользователей: <strong id="total-users">0</strong><br>
Кол-во игроков: <strong id="total-gamers">0</strong>
</div>
<div class="search-box">
<input type="text" id="search-input" placeholder="Поиск по имени или никнейму...">
</div>
<div class="users-list" id="users-list">
<div class="loading">Загрузка...</div>
</div>
</div>
<!-- Правая панель: Игроки -->
<div class="panel gamers-panel">
<h1>🎮 Отслеживаемые игроки</h1>
<div id="selected-user-info" style="display: none;">
<div class="selected-user-info">
<h2 id="selected-user-name">Выберите пользователя</h2>
<p id="selected-user-username"></p>
<p id="selected-user-date"></p>
</div>
</div>
<div id="gamers-table-container">
<div class="empty">Выберите пользователя для просмотра его игроков</div>
</div>
</div>
</div>
<script>
let users = [];
let selectedUserId = null;
let filteredUsers = [];
// Загрузка пользователей
async function loadUsers() {
try {
const response = await fetch('/api/users');
const data = await response.json();
if (data.success) {
users = data.users;
document.getElementById('total-users').textContent = data.total_users;
document.getElementById('total-gamers').textContent = data.total_gamers;
filteredUsers = users;
renderUsers();
// Выбираем первого пользователя по умолчанию
if (users.length > 0) {
selectUser(users[0].user_id);
}
}
} catch (error) {
console.error('Error loading users:', error);
document.getElementById('users-list').innerHTML = '<div class="empty">Ошибка загрузки данных</div>';
}
}
// Рендеринг пользователей
function renderUsers() {
const usersList = document.getElementById('users-list');
if (filteredUsers.length === 0) {
usersList.innerHTML = '<div class="empty">Пользователи не найдены</div>';
return;
}
usersList.innerHTML = filteredUsers.map(user => `
<div class="user-item ${selectedUserId === user.user_id ? 'active' : ''}" onclick="selectUser(${user.user_id})">
<div class="user-name">${escapeHtml(user.first_name)}</div>
<div class="user-info">@${escapeHtml(user.username)} • ID: ${user.user_id}</div>
<div class="user-info">Добавлен: ${formatDate(user.created_at)}</div>
<div class="user-stats">
<span>📊 ${user.gamer_count} игроков</span>
<span>✅ ${user.active_gamers} активен</span>
<span>⏰ ${user.monitored_gamers} с уведомл.</span>
</div>
</div>
`).join('');
}
// Выбор пользователя
async function selectUser(userId) {
selectedUserId = userId;
renderUsers();
// Находим выбранного пользователя
const user = users.find(u => u.user_id === userId);
if (user) {
document.getElementById('selected-user-info').style.display = 'block';
document.getElementById('selected-user-name').textContent = user.first_name;
document.getElementById('selected-user-username').textContent = `@${user.username} • ID: ${user.user_id}`;
document.getElementById('selected-user-date').textContent = `Добавлен: ${formatDate(user.created_at)}`;
}
// Загрузка игроков
await loadGamers(userId);
}
// Загрузка игроков пользователя
async function loadGamers(userId) {
try {
const response = await fetch(`/api/users/${userId}/gamers`);
const data = await response.json();
if (data.success) {
renderGamers(data.gamers);
} else {
document.getElementById('gamers-table-container').innerHTML =
'<div class="empty">Ошибка загрузки игроков</div>';
}
} catch (error) {
console.error('Error loading gamers:', error);
document.getElementById('gamers-table-container').innerHTML =
'<div class="empty">Ошибка загрузки данных</div>';
}
}
// Рендеринг игроков
function renderGamers(gamers) {
const container = document.getElementById('gamers-table-container');
if (gamers.length === 0) {
container.innerHTML = '<div class="empty">Нет отслеживаемых игроков</div>';
return;
}
const table = `
<table class="gamers-table">
<thead>
<tr>
<th>Игрок</th>
<th>Статус</th>
<th>Токен</th>
<th>Период уведомлений</th>
<th>Добавлен</th>
</tr>
</thead>
<tbody>
${gamers.map(gamer => `
<tr>
<td><strong>${escapeHtml(gamer.username)}</strong> (ID: ${gamer.id})</td>
<td>
<span class="badge ${gamer.is_active ? 'badge-success' : 'badge-secondary'}">
${gamer.is_active ? '✅ АКТИВЕН' : '⚪'}
</span>
</td>
<td>
<span class="badge badge-token">
${gamer.has_token ? '🔑 Есть' : '❌ Нет'}
</span>
</td>
<td>
${gamer.period_minutes > 0 ?
`${gamer.period_minutes} мин` :
'<span style="color: #999;">—</span>'}
</td>
<td style="color: #666;">${formatDate(gamer.created_at)}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
container.innerHTML = table;
}
// Поиск
document.getElementById('search-input').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
filteredUsers = users.filter(user => {
const name = (user.first_name || '').toLowerCase();
const username = (user.username || '').toLowerCase();
return name.includes(searchTerm) || username.includes(searchTerm);
});
renderUsers();
});
// Утилиты
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function formatDate(dateString) {
if (!dateString) return '—';
const date = new Date(dateString);
return date.toLocaleDateString('ru-RU') + ' ' + date.toLocaleTimeString('ru-RU', {
hour: '2-digit',
minute: '2-digit'
});
}
// Инициализация
loadUsers();
</script>
</body>
</html>