341 lines
11 KiB
HTML
341 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Просмотр серверных логов AI Drawer</title>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
margin: 0;
|
||
padding: 20px;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 20px;
|
||
border-bottom: 2px solid #007bff;
|
||
}
|
||
|
||
.status {
|
||
padding: 10px 15px;
|
||
border-radius: 5px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.status.success {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
}
|
||
|
||
.status.error {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
}
|
||
|
||
.status.warning {
|
||
background: #fff3cd;
|
||
color: #856404;
|
||
}
|
||
|
||
.controls {
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #007bff;
|
||
color: white;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #6c757d;
|
||
color: white;
|
||
}
|
||
|
||
.btn-danger {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
|
||
.btn:hover {
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.log-container {
|
||
background: #f8f9fa;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 5px;
|
||
padding: 15px;
|
||
max-height: 600px;
|
||
overflow-y: auto;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 12px;
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
.log-entry {
|
||
margin: 5px 0;
|
||
padding: 8px;
|
||
border-radius: 3px;
|
||
border-left: 4px solid #ccc;
|
||
}
|
||
|
||
.log-info {
|
||
border-left-color: #17a2b8;
|
||
background: #d1ecf1;
|
||
}
|
||
|
||
.log-success {
|
||
border-left-color: #28a745;
|
||
background: #d4edda;
|
||
}
|
||
|
||
.log-warning {
|
||
border-left-color: #ffc107;
|
||
background: #fff3cd;
|
||
}
|
||
|
||
.log-error {
|
||
border-left-color: #dc3545;
|
||
background: #f8d7da;
|
||
}
|
||
|
||
.log-debug {
|
||
border-left-color: #6c757d;
|
||
background: #e2e3e5;
|
||
}
|
||
|
||
.stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 15px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: #f8f9fa;
|
||
padding: 15px;
|
||
border-radius: 5px;
|
||
border-left: 4px solid #007bff;
|
||
}
|
||
|
||
.stat-title {
|
||
font-size: 12px;
|
||
color: #6c757d;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #007bff;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>📋 Серверные логи AI Drawer</h1>
|
||
<div id="server-status" class="status">Проверка...</div>
|
||
</div>
|
||
|
||
<div class="stats" id="stats-container">
|
||
<!-- Статистика будет загружена здесь -->
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<button class="btn btn-primary" onclick="loadLogs()">🔄 Обновить</button>
|
||
<button class="btn btn-secondary" onclick="loadLogs(100)">📄 Последние 100</button>
|
||
<button class="btn btn-secondary" onclick="loadLogs(500)">📄 Последние 500</button>
|
||
<button class="btn btn-danger" onclick="clearLogs()">🗑️ Очистить все</button>
|
||
<button class="btn btn-secondary" onclick="exportLogs()">💾 Экспорт</button>
|
||
</div>
|
||
|
||
<div id="log-container" class="log-container">
|
||
Загрузка логов...
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let currentLogs = [];
|
||
|
||
function updateStatus(message, type = 'info') {
|
||
const statusEl = document.getElementById('server-status');
|
||
statusEl.textContent = message;
|
||
statusEl.className = `status ${type}`;
|
||
}
|
||
|
||
function updateStats(logs) {
|
||
const statsContainer = document.getElementById('stats-container');
|
||
|
||
const totalLogs = logs.length;
|
||
const errorLogs = logs.filter(log => log.data.type === 'error').length;
|
||
const warningLogs = logs.filter(log => log.data.type === 'warning').length;
|
||
const successLogs = logs.filter(log => log.data.type === 'success').length;
|
||
|
||
const uniqueDevices = new Set(logs.map(log => log.data.screenSize)).size;
|
||
const uniqueBrowsers = new Set(logs.map(log => log.data.userAgent?.split(' ')[0])).size;
|
||
|
||
statsContainer.innerHTML = `
|
||
<div class="stat-card">
|
||
<div class="stat-title">Всего записей</div>
|
||
<div class="stat-value">${totalLogs}</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-title">Ошибки</div>
|
||
<div class="stat-value" style="color: #dc3545;">${errorLogs}</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-title">Предупреждения</div>
|
||
<div class="stat-value" style="color: #ffc107;">${warningLogs}</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-title">Успешные</div>
|
||
<div class="stat-value" style="color: #28a745;">${successLogs}</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-title">Устройства</div>
|
||
<div class="stat-value">${uniqueDevices}</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-title">Браузеры</div>
|
||
<div class="stat-value">${uniqueBrowsers}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function displayLogs(logs) {
|
||
const container = document.getElementById('log-container');
|
||
currentLogs = logs;
|
||
|
||
if (logs.length === 0) {
|
||
container.innerHTML = 'Логи не найдены';
|
||
return;
|
||
}
|
||
|
||
const logHTML = logs.map(log => {
|
||
const type = log.data.type || 'info';
|
||
const details = log.data.details ?
|
||
`\nДетали: ${JSON.stringify(log.data.details, null, 2)}` : '';
|
||
|
||
return `
|
||
<div class="log-entry log-${type}">
|
||
<strong>[${log.timestamp}] ${log.data.type?.toUpperCase() || 'INFO'}:</strong> ${log.data.message}
|
||
${details}
|
||
<div style="margin-top: 5px; font-size: 10px; color: #6c757d;">
|
||
Устройство: ${log.data.screenSize || 'N/A'} |
|
||
Браузер: ${log.data.userAgent?.split(' ')[0] || 'N/A'}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
container.innerHTML = logHTML;
|
||
updateStats(logs);
|
||
}
|
||
|
||
function loadLogs(limit = 50) {
|
||
updateStatus('Загрузка логов...', 'info');
|
||
|
||
fetch(`/test_log_server.php?action=read&limit=${limit}`)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
displayLogs(data.logs);
|
||
updateStatus(`Загружено ${data.logs.length} записей`, 'success');
|
||
} else {
|
||
updateStatus(`Ошибка: ${data.message}`, 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
updateStatus(`Ошибка подключения: ${error.message}`, 'error');
|
||
});
|
||
}
|
||
|
||
function clearLogs() {
|
||
if (!confirm('Вы уверены, что хотите очистить все логи?')) {
|
||
return;
|
||
}
|
||
|
||
updateStatus('Очистка логов...', 'warning');
|
||
|
||
fetch('/test_log_server.php?action=clear')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
updateStatus('Логи очищены', 'success');
|
||
document.getElementById('log-container').innerHTML = 'Логи очищены';
|
||
document.getElementById('stats-container').innerHTML = '';
|
||
} else {
|
||
updateStatus(`Ошибка: ${data.message}`, 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
updateStatus(`Ошибка подключения: ${error.message}`, 'error');
|
||
});
|
||
}
|
||
|
||
function exportLogs() {
|
||
if (currentLogs.length === 0) {
|
||
alert('Нет логов для экспорта');
|
||
return;
|
||
}
|
||
|
||
const logText = currentLogs.map(log => {
|
||
const details = log.data.details ?
|
||
`\nДетали: ${JSON.stringify(log.data.details, null, 2)}` : '';
|
||
|
||
return `[${log.timestamp}] ${log.data.type?.toUpperCase() || 'INFO'}: ${log.data.message}${details}`;
|
||
}).join('\n\n');
|
||
|
||
const blob = new Blob([logText], { type: 'text/plain' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `ai-drawer-server-logs-${new Date().toISOString().slice(0, 19)}.txt`;
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
|
||
updateStatus('Логи экспортированы', 'success');
|
||
}
|
||
|
||
// Загружаем логи при загрузке страницы
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
loadLogs();
|
||
|
||
// Автообновление каждые 30 секунд
|
||
setInterval(() => {
|
||
loadLogs();
|
||
}, 30000);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|
||
|