/
Shtirlitc
/
TickBot
Обзор
Документация
Войти
/
Shtirlitc
/
TickBot
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
log_server/logger.html
430 строк
14 KB
Shtirlyts
Initial commit: TickBot attendance bot with stale-status fixes
31 июл 2026, 23:40
31 июл 2026, 23:40
30e955c
Код
Авторство
О чём код?
<!-- logger.html --> <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8" /> <title>Bot Logs - ATB TAI 251</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <style> :root { --bg: #1e1e2e; --text: #cdd6f4; --panel: #181825; --border: #313244; --input-bg: #11111b; --btn: #89b4fa; --btn-hover: #b4befe; --info: #89b4fa; --debug: #74c7ec; --warn: #fab387; --error: #f38ba8; --success: #a6e3a1; --system: #cba6f7; --flow: #f9e2af; --click: #94e2d5; --admin: #f38ba8; --payment: #fab387; } body { font-family: 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 20px; } .top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } h1 { margin: 0; font-size: 1.5em; } .controls { display: flex; gap: 10px; flex-wrap: wrap; background: var(--panel); padding: 12px; border-radius: 8px; border: 1px solid var(--border); } input, select, button { padding: 6px 10px; background: var(--input-bg); color: var(--text); border: 1px solid var(--border); border-radius: 4px; } button { background: var(--btn); color: var(--bg); cursor: pointer; font-weight: 500; } button:hover { background: var(--btn-hover); } #logs { background: var(--panel); padding: 15px; border-radius: 8px; border: 1px solid var(--border); max-height: 75vh; overflow-y: auto; line-height: 1.5; direction: ltr; text-align: left; /* Новые логи внизу */ display: flex; flex-direction: column-reverse; } .log-line { white-space: pre-line; padding: 2px 0; font-family: 'Courier New', monospace; font-size: 0.95em; } .info { color: var(--info); } .warn { color: var(--warn); font-weight: bold; } .error { color: var(--error); font-weight: bold; } .debug { color: var(--debug); } .system { color: var(--system); } .user_flow { color: var(--flow); } .user_click { color: var(--click); } .admin_action { color: var(--admin); } .payment { color: var(--payment); } .info::before { content: "✅ "; } .warn::before { content: "⚠️ "; } .error::before { content: "❌ "; } .debug::before { content: "🔍 "; } .system::before { content: "⚙️ "; } .user_flow::before { content: "🟢 "; } .user_click::before { content: "🖱️ "; } .admin_action::before { content: "🛠️ "; } .payment::before { content: "⭐ "; } .stats { background: rgba(137, 180, 250, 0.1); padding: 8px 12px; border-radius: 6px; font-size: 0.9em; margin-bottom: 15px; border: 1px solid var(--border); } </style> </head> <body> <div class="top"> <h1>🤖 Bot Logs - ATB TAI 251</h1> <button onclick="toggleTheme()">🌓</button> </div> <div class="stats" id="stats">Загрузка логов...</div> <div class="controls"> <input type="text" id="search" placeholder="Поиск..." /> <select id="level"> <option value="">Все уровни</option> <option value="INFO">INFO</option> <option value="WARNING">WARNING</option> <option value="ERROR">ERROR</option> <option value="DEBUG">DEBUG</option> </select> <select id="type"> <option value="">Все типы</option> <option value="user_click">🖱️ Клики</option> <option value="user_flow">🟢 Действия</option> <option value="user_error">🔴 Ошибки</option> <option value="admin_action">🛠️ Админ</option> <option value="payment">⭐ Поддержка</option> <option value="system">⚙️ Система</option> </select> <button onclick="filterLogs()">🔍 Применить</button> <button onclick="clearFilters()">Сбросить</button> <label><input type="checkbox" id="autoRefresh" checked />Автообновление</label> </div> <div id="logs"></div> <script> const logsEl = document.getElementById('logs'); const statsEl = document.getElementById('stats'); const searchEl = document.getElementById('search'); const levelEl = document.getElementById('level'); const typeEl = document.getElementById('type'); const autoRefreshEl = document.getElementById('autoRefresh'); let allLogs = []; let filteredLogs = []; let timer; // Добавляем обработчики событий для автоматического применения фильтров levelEl.addEventListener('change', filterLogs); typeEl.addEventListener('change', filterLogs); searchEl.addEventListener('input', filterLogs); // Определение типа действия — надёжнее function detectType(message) { if (message.includes('[SYSTEM]')) return 'system'; if (message.includes('[USER_FLOW]')) return 'user_flow'; if (message.includes('[USER_ERROR]')) return 'user_error'; if (message.includes('Callback:') || message.includes('кнопка') || /\/start/.test(message)) return 'user_click'; if (message.includes('Admin') || message.includes('админ')) return 'admin_action'; if (message.includes('DONATE') || message.includes('поддержка')) return 'payment'; return 'unknown'; } // Парсинг строки лога — теперь работает с твоим форматом function parseLine(line) { // Формат: [2026-04-30 09:50:27] __main__ - INFO - 👋 Бот остановлен пользователем const match = line.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] ([^ ]+) - (INFO|WARNING|ERROR|DEBUG) - (.*)$/); if (!match) { return null; // пропускаем непарсящиеся строки } const [, timestamp, logger, level, msg] = match; const time = timestamp.split(' ')[1]; // HH:MM:SS const type = detectType(msg); return { raw: line, time, type, level, message: msg.trim() }; } // Фильтрация мусора function isGarbage(msg) { return [ 'functools.partial', 'operation ... completed', 'executing <function', 'Executing SQL', 'Sleep for', 'try again', 'Middleware registered', 'Polling started' ].some(term => msg.includes(term)); } // Фильтры function filterLogs() { const search = searchEl.value.trim().toLowerCase(); const level = levelEl.value; const type = typeEl.value; filteredLogs = allLogs.filter(log => { if (!log) return false; if (level && log.level !== level) return false; if (type && log.type !== type) return false; if (search && !log.raw.toLowerCase().includes(search)) return false; return true; }); renderLogs(); } // Отображение — новые внизу function renderLogs() { logsEl.innerHTML = ''; // очищаем if (filteredLogs.length === 0) { const el = document.createElement('div'); el.className = 'log-line'; el.textContent = "Нет логов или ничего не найдено. ¯\\_(ツ)_/¯"; logsEl.appendChild(el); return; } // Показываем последние 1000 const slice = filteredLogs.slice(-1000); for (const log of slice) { const el = document.createElement('div'); el.className = `log-line ${log.type}`; el.textContent = `[${log.time}] ${log.message}`; logsEl.appendChild(el); } statsEl.textContent = `Показано: ${filteredLogs.length} / ${allLogs.length} строк`; } // Загрузка async function loadLogs() { try { const res = await fetch('logs/bot.log?' + Date.now()); const text = await res.text(); allLogs = text.split('\n') .filter(l => l.trim()) .map(parseLine) .filter(log => log && !isGarbage(log.raw)) .slice(-5000); // только последние 5000 filterLogs(); } catch (e) { logsEl.innerHTML = ''; const el = document.createElement('div'); el.className = 'log-line error'; el.textContent = 'Ошибка загрузки логов: ' + e.message; logsEl.appendChild(el); } } function refreshLogs() { loadLogs(); } function clearFilters() { searchEl.value = ''; levelEl.value = ''; typeEl.value = ''; filterLogs(); } function toggleTheme() { const isDark = document.body.style.backgroundColor !== 'white'; document.body.style.backgroundColor = isDark ? 'white' : ''; document.body.style.color = isDark ? 'black' : ''; } function startAutoRefresh() { clearInterval(timer); if (autoRefreshEl.checked) { timer = setInterval(refreshLogs, 3000); } } autoRefreshEl.addEventListener('change', startAutoRefresh); // WebSocket соединение let ws = null; let isConnected = false; let reconnectTimeout = null; const reconnectInterval = 3000; // 3 секунды // Получаем токен из localStorage или используем значение по умолчанию function getToken() { return localStorage.getItem('logServerToken') || 'secure_token_12345'; } // Сохраняем токен в localStorage function setToken(token) { localStorage.setItem('logServerToken', token); } // Подключение к WebSocket function connectWebSocket() { if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) { return; } try { // Создаем WebSocket соединение ws = new WebSocket(`ws://${window.location.host}/ws`); ws.onopen = function() { console.log('WebSocket connected'); isConnected = true; statsEl.textContent = 'Подключение установлено...'; // Отправляем токен для аутентификации const token = getToken(); ws.send(JSON.stringify({token: token})); }; ws.onmessage = function(event) { try { const data = JSON.parse(event.data); switch(data.type) { case 'auth': if (data.status === 'success') { statsEl.textContent = 'Аутентификация успешна. Загрузка логов...'; // После успешной аутентификации сервер отправит последние логи } else { statsEl.textContent = `Ошибка аутентификации: ${data.message}`; // Предлагаем ввести токен const token = prompt('Введите токен аутентификации:', getToken()); if (token) { setToken(token); ws.send(JSON.stringify({token: token})); } } break; case 'logs': // Получили последние логи allLogs = data.lines.map(parseLine).filter(log => log && !isGarbage(log.raw)); filterLogs(); statsEl.textContent = `Загружено: ${allLogs.length} строк`; break; case 'log': // Получили новую строку лога const newLog = parseLine(data.line); if (newLog && !isGarbage(newLog.raw)) { allLogs.push(newLog); // Ограничиваем количество логов в памяти if (allLogs.length > 10000) { allLogs = allLogs.slice(-5000); } filterLogs(); } break; case 'pong': // Ответ на ping break; default: console.log('Unknown message type:', data.type); } } catch (e) { console.error('Error processing message:', e); } }; ws.onclose = function() { console.log('WebSocket disconnected'); isConnected = false; statsEl.textContent = 'Потеряно соединение. Повторное подключение...'; // Пытаемся переподключиться if (reconnectTimeout) { clearTimeout(reconnectTimeout); } reconnectTimeout = setTimeout(connectWebSocket, reconnectInterval); }; ws.onerror = function(error) { console.error('WebSocket error:', error); statsEl.textContent = 'Ошибка соединения'; }; } catch (e) { console.error('Error connecting to WebSocket:', e); statsEl.textContent = 'Ошибка подключения: ' + e.message; } } // Отправка ping для проверки соединения function sendPing() { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({action: 'ping'})); } } // Запуск WebSocket соединения function startWebSocket() { connectWebSocket(); // Отправляем ping каждые 30 секунд setInterval(sendPing, 30000); } // Старт startWebSocket(); </script> </body> </html>