Files
go-service/cmd/server/web/logs.html

385 lines
10 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">
<title>Журнал логов</title>
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="stylesheet" href="/css/style.css">
<style>
/* Дополнительные стили для журнала */
.logs-container {
display: flex;
gap: 20px;
height: calc(100vh - 120px);
}
.logs-sidebar {
flex: 0 0 280px;
border-right: 1px solid #00ff88;
padding-right: 16px;
overflow-y: auto;
}
.logs-content {
flex: 1;
overflow-y: auto;
}
.log-file-item {
padding: 8px 12px;
margin: 4px 0;
cursor: pointer;
border-radius: 4px;
border: 1px solid transparent;
transition: all 0.2s;
}
.log-file-item:hover {
background: rgba(0,255,136,0.1);
border-color: #00ff88;
}
.log-file-item.active {
background: rgba(0,255,136,0.2);
border-color: #00ff88;
}
.log-file-item .file-name {
font-size: 13px;
}
.log-file-item .file-info {
font-size: 11px;
opacity: 0.6;
}
.log-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.log-stat-card {
padding: 12px;
border: 1px solid #00ff88;
border-radius: 4px;
text-align: center;
}
.log-stat-card .value {
font-size: 24px;
font-weight: bold;
}
.log-stat-card .label {
font-size: 11px;
opacity: 0.6;
}
.data-table {
width: 100%;
font-size: 12px;
border-collapse: collapse;
}
.data-table th {
text-align: left;
padding: 6px 8px;
border-bottom: 1px solid #00ff88;
position: sticky;
top: 0;
background: #050805;
}
.data-table td {
padding: 4px 8px;
border-bottom: 1px solid rgba(0,255,136,0.1);
}
.data-table tr:hover {
background: rgba(0,255,136,0.05);
}
.strength-0 { color: #666; }
.strength-1 { color: #88ff88; }
.strength-2 { color: #ffaa44; }
.strength-3 { color: #ff4444; }
.events-list {
font-family: monospace;
font-size: 13px;
line-height: 1.6;
}
.events-list .event-time {
opacity: 0.5;
}
.tabs {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
.tab-btn {
padding: 6px 16px;
background: transparent;
color: #00ff88;
border: 1px solid #00ff88;
border-radius: 4px;
cursor: pointer;
font-family: monospace;
}
.tab-btn.active {
background: rgba(0,255,136,0.2);
}
.tab-btn:hover {
background: rgba(0,255,136,0.1);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
</style>
</head>
<body>
<div class="header-bar">
<h1>📋 ЖУРНАЛ ЛОГОВ</h1>
<div id="server-time" class="header-time">--:--:--</div>
<a href="/" style="color:#00ff88; text-decoration:none; font-size:14px;">На главную</a>
</div>
<div class="logs-container">
<div class="logs-sidebar">
<h3 style="margin-top:0;">📁 Файлы данных</h3>
<div id="fileList"></div>
</div>
<div class="logs-content">
<div class="tabs">
<button class="tab-btn active" data-tab="data">📊 Данные</button>
<button class="tab-btn" data-tab="events">📋 События</button>
</div>
<div id="tab-data" class="tab-content active">
<div id="logStats" class="log-stats"></div>
<div id="chartContainer" style="margin-bottom:16px;">
<canvas id="logChart" height="200"></canvas>
</div>
<div id="dataTableContainer" style="max-height:400px; overflow-y:auto;"></div>
</div>
<div id="tab-events" class="tab-content">
<div id="eventsContent" class="events-list">Загрузка...</div>
</div>
</div>
</div>
<script type="module">
import { drawChart } from '/dist/chart.js';
// Состояние
let currentFile = null;
let currentData = null;
let currentEvents = [];
// DOM элементы
const fileList = document.getElementById('fileList');
const logStats = document.getElementById('logStats');
const dataTableContainer = document.getElementById('dataTableContainer');
const logChart = document.getElementById('logChart');
const eventsContent = document.getElementById('eventsContent');
// Загрузка списка файлов
async function loadFileList() {
try {
const response = await fetch('/api/log/files');
const files = await response.json();
fileList.innerHTML = '';
files.forEach(file => {
const div = document.createElement('div');
div.className = 'log-file-item';
if (currentFile === file.name) div.classList.add('active');
div.innerHTML = `
<div class="file-name">${file.name}</div>
<div class="file-info">${file.time} | ${(file.size/1024).toFixed(1)} KB</div>
`;
div.addEventListener('click', () => loadFileData(file.name));
fileList.appendChild(div);
});
// Автоматически загружаем первый файл
if (files.length > 0 && !currentFile) {
loadFileData(files[0].name);
}
} catch (error) {
console.error('Error loading file list:', error);
}
}
// Загрузка данных файла
async function loadFileData(filename) {
currentFile = filename;
// Обновляем активный элемент в списке
document.querySelectorAll('.log-file-item').forEach(el => el.classList.remove('active'));
const items = document.querySelectorAll('.log-file-item');
for (const item of items) {
if (item.textContent.includes(filename)) {
item.classList.add('active');
break;
}
}
try {
const response = await fetch(`/api/log/data?file=${encodeURIComponent(filename)}`);
const data = await response.json();
currentData = data;
renderData(data);
} catch (error) {
console.error('Error loading data:', error);
}
}
// Отрисовка данных
function renderData(data) {
const samples = data.samples || [];
// Статистика
if (samples.length > 0) {
const totalPoints = samples.length;
// Амплитуда > 0 (старшие 2 бита)
const amplitudeOver0Count = samples.filter(s => s.amplitude > 0).length;
// Сигнал > 10 (младшие 6 бит)
const signalOver10Count = samples.filter(s => s.signal > 10).length;
// Общее количество объектов (сумма всех сигналов)
const totalObjects = samples.reduce((sum, s) => sum + s.signal, 0);
logStats.innerHTML = `
<div class="log-stat-card">
<div class="value">${totalPoints}</div>
<div class="label">Всего точек</div>
</div>
<div class="log-stat-card">
<div class="value">${amplitudeOver0Count}</div>
<div class="label">Амплитуда > 0</div>
</div>
<div class="log-stat-card">
<div class="value">${signalOver10Count}</div>
<div class="label">Сигнал > 10</div>
</div>
<div class="log-stat-card">
<div class="value">${totalObjects}</div>
<div class="label">Объектов всего</div>
</div>
`;
}
// График
if (logChart && samples.length > 0) {
const history = samples.map(s => s.signal); // Используем signal вместо count
drawChart(logChart, history);
}
// Таблица - убрали колонку "Объектов"
if (samples.length > 0) {
let html = `<table class="data-table">
<thead>
<tr>
<th>Время</th>
<th>Амплитуда</th>
<th>Сигнал</th>
</tr>
</thead>
<tbody>`;
const displaySamples = samples.slice(-200);
displaySamples.forEach(s => {
// Цвет в зависимости от амплитуды
let ampClass = '';
if (s.amplitude === 0) ampClass = 'strength-0';
else if (s.amplitude === 1) ampClass = 'strength-1';
else if (s.amplitude === 2) ampClass = 'strength-2';
else if (s.amplitude === 3) ampClass = 'strength-3';
html += `<tr>
<td>${s.time}</td>
<td class="${ampClass}">${s.amplitude}</td>
<td>${s.signal}</td>
</tr>`;
});
html += '</tbody></table>';
dataTableContainer.innerHTML = html;
} else {
dataTableContainer.innerHTML = '<p>Нет данных</p>';
}
}
// Загрузка событий
async function loadEvents() {
try {
const response = await fetch('/api/log/events?limit=200');
const data = await response.json();
currentEvents = data.events || [];
let html = '';
currentEvents.forEach(e => {
html += `<div><span class="event-time">[${e.time}]</span> ${e.event}</div>`;
});
eventsContent.innerHTML = html || 'Нет событий';
} catch (error) {
eventsContent.innerHTML = 'Ошибка загрузки событий';
}
}
// Переключение табов
document.querySelectorAll('.tab-btn[data-tab]').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-btn[data-tab]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const tabName = btn.dataset.tab;
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
document.getElementById(`tab-${tabName}`).classList.add('active');
if (tabName === 'events') {
loadEvents();
}
});
});
// Инициализация
loadFileList();
// Обновление времени с сервера (единый формат)
async function updateTime() {
try {
const response = await fetch('/api/health');
const data = await response.json();
document.getElementById('server-time').textContent = data.server_time || new Date().toLocaleTimeString();
} catch (error) {
// Если сервер недоступен, используем локальное время
const now = new Date();
document.getElementById('server-time').textContent = now.toLocaleTimeString();
}
}
setInterval(updateTime, 1000);
updateTime();
</script>
</body>
</html>