// LOGPile Frontend Application document.addEventListener('DOMContentLoaded', () => { initUpload(); initTabs(); initFilters(); }); // Upload handling function initUpload() { const dropZone = document.getElementById('drop-zone'); const fileInput = document.getElementById('file-input'); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('dragover'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('dragover'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('dragover'); const files = e.dataTransfer.files; if (files.length > 0) { uploadFile(files[0]); } }); fileInput.addEventListener('change', () => { if (fileInput.files.length > 0) { uploadFile(fileInput.files[0]); } }); } async function uploadFile(file) { const status = document.getElementById('upload-status'); status.textContent = 'Загрузка и анализ...'; status.className = ''; const formData = new FormData(); formData.append('archive', file); try { const response = await fetch('/api/upload', { method: 'POST', body: formData }); const result = await response.json(); if (response.ok) { status.innerHTML = `${escapeHtml(result.vendor)}
` + `${result.stats.sensors} сенсоров, ${result.stats.fru} компонентов, ${result.stats.events} событий`; status.className = 'success'; loadData(result.vendor); } else { status.textContent = result.error || 'Ошибка загрузки'; status.className = 'error'; } } catch (err) { status.textContent = 'Ошибка соединения'; status.className = 'error'; } } // Tab navigation function initTabs() { const tabs = document.querySelectorAll('.tab'); tabs.forEach(tab => { tab.addEventListener('click', () => { tabs.forEach(t => t.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); tab.classList.add('active'); document.getElementById(tab.dataset.tab).classList.add('active'); }); }); } // Filters function initFilters() { document.getElementById('sensor-filter').addEventListener('change', (e) => { filterSensors(e.target.value); }); document.getElementById('severity-filter').addEventListener('change', (e) => { filterEvents(e.target.value); }); document.getElementById('serial-filter').addEventListener('change', (e) => { filterSerials(e.target.value); }); } let allSensors = []; let allEvents = []; let allSerials = []; let currentVendor = ''; // Load data from API async function loadData(vendor) { currentVendor = vendor || ''; document.getElementById('upload-section').classList.add('hidden'); document.getElementById('data-section').classList.remove('hidden'); document.getElementById('clear-btn').classList.remove('hidden'); // Update vendor badge if exists const vendorBadge = document.getElementById('vendor-badge'); if (vendorBadge && currentVendor) { vendorBadge.textContent = currentVendor; vendorBadge.classList.remove('hidden'); } await Promise.all([ loadConfig(), loadFirmware(), loadSensors(), loadSerials(), loadEvents() ]); } async function loadConfig() { try { const response = await fetch('/api/config'); const config = await response.json(); renderConfig(config); } catch (err) { console.error('Failed to load config:', err); } } function renderConfig(data) { const container = document.getElementById('config-content'); if (!data || Object.keys(data).length === 0) { container.innerHTML = '

Нет данных о конфигурации

'; return; } // Handle both old format (direct hardware) and new format (with specification) const config = data.hardware || data; const spec = data.specification; let html = ''; // Specification summary (new section) if (spec && spec.length > 0) { html += '

Спецификация сервера

'; } // CPUs if (config.cpus && config.cpus.length > 0) { html += '

Процессоры

'; config.cpus.forEach(cpu => { html += `
Socket ${cpu.socket}: ${escapeHtml(cpu.model)}
Ядра: ${cpu.cores}, Потоки: ${cpu.threads}
Частота: ${cpu.frequency_mhz} MHz (Turbo: ${cpu.max_frequency_mhz} MHz)
TDP: ${cpu.tdp_w}W, L3: ${Math.round(cpu.l3_cache_kb/1024)} MB
`; }); html += '
'; } // Memory summary if (config.memory && config.memory.length > 0) { const totalGB = config.memory.reduce((sum, m) => sum + m.size_mb, 0) / 1024; html += `

Память

Всего: ${totalGB} GB (${config.memory.length} модулей ${config.memory[0].type} @ ${config.memory[0].speed_mhz} MHz)

Производитель: ${escapeHtml(config.memory[0].manufacturer)}

`; } // Storage summary if (config.storage && config.storage.length > 0) { html += '

Накопители

'; config.storage.forEach(s => { html += ``; }); html += '
СлотМодельРазмер
${escapeHtml(s.slot || s.interface)} ${escapeHtml(s.model)} ${s.size_gb} GB
'; } // PCIe summary if (config.pcie_devices && config.pcie_devices.length > 0) { html += '

PCIe устройства

'; config.pcie_devices.forEach(p => { html += ``; }); html += '
СлотТипПроизводительСкорость
${escapeHtml(p.slot)} ${escapeHtml(p.device_class)} ${escapeHtml(p.manufacturer || '-')} x${p.link_width} ${escapeHtml(p.link_speed)}
'; } container.innerHTML = html; } async function loadFirmware() { try { const response = await fetch('/api/firmware'); const firmware = await response.json(); renderFirmware(firmware); } catch (err) { console.error('Failed to load firmware:', err); } } function renderFirmware(firmware) { const tbody = document.querySelector('#firmware-table tbody'); tbody.innerHTML = ''; if (!firmware || firmware.length === 0) { tbody.innerHTML = 'Нет данных о прошивках'; return; } firmware.forEach(fw => { const row = document.createElement('tr'); row.innerHTML = ` ${escapeHtml(fw.device_name)} ${escapeHtml(fw.version)} ${escapeHtml(fw.build_date || '-')} `; tbody.appendChild(row); }); } async function loadSensors() { try { const response = await fetch('/api/sensors'); allSensors = await response.json(); renderSensors(allSensors); } catch (err) { console.error('Failed to load sensors:', err); } } function renderSensors(sensors) { const container = document.getElementById('sensors-content'); if (!sensors || sensors.length === 0) { container.innerHTML = '

Нет данных о сенсорах

'; return; } // Group by type const byType = {}; sensors.forEach(s => { if (!byType[s.type]) byType[s.type] = []; byType[s.type].push(s); }); const typeNames = { temperature: 'Температура', voltage: 'Напряжение', power: 'Мощность', fan_speed: 'Вентиляторы', fan_status: 'Статус вентиляторов', psu_status: 'Статус БП', cpu_status: 'Статус CPU', storage_status: 'Статус накопителей', other: 'Прочее' }; let html = ''; for (const [type, items] of Object.entries(byType)) { html += `

${typeNames[type] || type}

`; items.forEach(s => { let valueStr = ''; let statusClass = s.status === 'ok' ? 'ok' : (s.status === 'ns' ? 'ns' : 'warn'); if (s.value) { valueStr = `${s.value} ${s.unit}`; } else if (s.raw_value) { valueStr = s.raw_value; } else { valueStr = s.status; } html += `
${escapeHtml(s.name)} ${escapeHtml(valueStr)}
`; }); html += '
'; } container.innerHTML = html; } function filterSensors(type) { if (!type) { renderSensors(allSensors); return; } const filtered = allSensors.filter(s => s.type === type); renderSensors(filtered); } async function loadSerials() { try { const response = await fetch('/api/serials'); allSerials = await response.json(); renderSerials(allSerials); } catch (err) { console.error('Failed to load serials:', err); } } function renderSerials(serials) { const tbody = document.querySelector('#serials-table tbody'); tbody.innerHTML = ''; if (!serials || serials.length === 0) { tbody.innerHTML = 'Нет серийных номеров'; return; } const categoryNames = { 'Board': 'Мат. плата', 'CPU': 'Процессор', 'Memory': 'Память', 'Storage': 'Накопитель', 'PCIe': 'PCIe', 'Network': 'Сеть', 'PSU': 'БП', 'FRU': 'FRU' }; serials.forEach(item => { if (!item.serial_number) return; const row = document.createElement('tr'); row.innerHTML = ` ${categoryNames[item.category] || item.category} ${escapeHtml(item.component)} ${escapeHtml(item.serial_number)} ${escapeHtml(item.manufacturer || '-')} ${escapeHtml(item.part_number || '-')} `; tbody.appendChild(row); }); } function filterSerials(category) { if (!category) { renderSerials(allSerials); return; } const filtered = allSerials.filter(s => s.category === category); renderSerials(filtered); } async function loadEvents() { try { const response = await fetch('/api/events'); allEvents = await response.json(); renderEvents(allEvents); } catch (err) { console.error('Failed to load events:', err); } } function renderEvents(events) { const tbody = document.querySelector('#events-table tbody'); tbody.innerHTML = ''; if (!events || events.length === 0) { tbody.innerHTML = 'Нет событий'; return; } events.forEach(event => { const row = document.createElement('tr'); row.innerHTML = ` ${formatDate(event.timestamp)} ${escapeHtml(event.source)} ${escapeHtml(event.description)} ${event.severity} `; tbody.appendChild(row); }); } function filterEvents(severity) { if (!severity) { renderEvents(allEvents); return; } const filtered = allEvents.filter(e => e.severity === severity); renderEvents(filtered); } // Export functions function exportData(format) { window.location.href = `/api/export/${format}`; } // Clear data async function clearData() { try { await fetch('/api/clear', { method: 'DELETE' }); document.getElementById('upload-section').classList.remove('hidden'); document.getElementById('data-section').classList.add('hidden'); document.getElementById('clear-btn').classList.add('hidden'); document.getElementById('upload-status').textContent = ''; allSensors = []; allEvents = []; allSerials = []; } catch (err) { console.error('Failed to clear data:', err); } } // Utilities function formatDate(isoString) { if (!isoString) return '-'; const date = new Date(isoString); return date.toLocaleString('ru-RU'); } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }