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>
|
|||
|
|
|
|||
|
|
|