// 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; } const config = data.hardware || data; const spec = data.specification; let html = ''; // Configuration sub-tabs html += `
`; // Specification tab html += '
'; if (spec && spec.length > 0) { html += '

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

'; } else { html += '

Нет данных о спецификации

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

Процессоры

'; config.cpus.forEach(cpu => { html += ``; }); html += '
SocketМодельЯдраПотокиЧастотаMax TurboTDPL3 CachePPIN
CPU${cpu.socket} ${escapeHtml(cpu.model)} ${cpu.cores} ${cpu.threads} ${cpu.frequency_mhz} MHz ${cpu.max_frequency_mhz} MHz ${cpu.tdp_w}W ${Math.round(cpu.l3_cache_kb/1024)} MB ${escapeHtml(cpu.ppin || '-')}
'; } else { html += '

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

'; } html += '
'; // Memory tab html += '
'; if (config.memory && config.memory.length > 0) { const totalGB = config.memory.reduce((sum, m) => sum + m.size_mb, 0) / 1024; const presentCount = config.memory.filter(m => m.present !== false).length; const workingCount = config.memory.filter(m => m.size_mb > 0).length; html += `

Модули памяти

${totalGB} GBВсего
${presentCount}Установлено
${workingCount}Активно
`; config.memory.forEach(mem => { const present = mem.present !== false ? '✓' : '-'; const presentClass = mem.present !== false ? 'present-yes' : 'present-no'; const sizeGB = mem.size_mb / 1024; const statusClass = (mem.status === 'OK' || !mem.status) ? '' : 'status-warning'; const rowClass = sizeGB === 0 ? 'row-warning' : ''; html += ``; }); html += '
LocationНаличиеРазмерТипMax частотаТекущая частотаПроизводительАртикулСтатус
${escapeHtml(mem.location || mem.slot)} ${present} ${sizeGB} GB ${escapeHtml(mem.type || '-')} ${mem.max_speed_mhz || '-'} MHz ${mem.current_speed_mhz || mem.speed_mhz || '-'} MHz ${escapeHtml(mem.manufacturer || '-')} ${escapeHtml(mem.part_number || '-')} ${escapeHtml(mem.status || 'OK')}
'; } else { html += '

Нет данных о памяти

'; } html += '
'; // Power tab html += '
'; if (config.power_supplies && config.power_supplies.length > 0) { html += '

Блоки питания

'; config.power_supplies.forEach(psu => { const statusClass = psu.status === 'OK' ? '' : 'status-warning'; html += ``; }); html += '
СлотПроизводительМодельМощностьВходВыходНапряжениеТемператураСтатус
${escapeHtml(psu.slot)} ${escapeHtml(psu.vendor || '-')} ${escapeHtml(psu.model || '-')} ${psu.wattage_w || '-'}W ${psu.input_power_w || '-'}W ${psu.output_power_w || '-'}W ${psu.input_voltage ? psu.input_voltage.toFixed(0) : '-'}V ${psu.temperature_c || '-'}°C ${escapeHtml(psu.status || '-')}
'; } else { html += '

Нет данных о блоках питания

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

Накопители

'; config.storage.forEach(s => { html += ``; }); html += '
СлотТипИнтерфейсМодельПроизводительРазмерСерийный номер
${escapeHtml(s.slot || '-')} ${escapeHtml(s.type || '-')} ${escapeHtml(s.interface || '-')} ${escapeHtml(s.model || '-')} ${escapeHtml(s.manufacturer || '-')} ${s.size_gb} GB ${escapeHtml(s.serial_number || '-')}
'; } else { html += '

Нет данных о накопителях

'; } html += '
'; // GPU tab html += '
'; if (config.gpus && config.gpus.length > 0) { html += '

Графические процессоры

'; config.gpus.forEach(gpu => { html += ``; }); html += '
СлотМодельПроизводительBDFPCIeСерийный номер
${escapeHtml(gpu.slot || '-')} ${escapeHtml(gpu.model || '-')} ${escapeHtml(gpu.manufacturer || '-')} ${escapeHtml(gpu.bdf || '-')} x${gpu.link_width || '-'} ${escapeHtml(gpu.link_speed || '-')} ${escapeHtml(gpu.serial_number || '-')}
'; } else { html += '

Нет GPU

'; } html += '
'; // Network tab html += '
'; if (config.network_adapters && config.network_adapters.length > 0) { html += '

Сетевые адаптеры

'; config.network_adapters.forEach(nic => { const macs = nic.mac_addresses ? nic.mac_addresses.join(', ') : '-'; const statusClass = nic.status === 'OK' ? '' : 'status-warning'; html += ``; }); html += '
СлотМодельПроизводительПортыТипMAC адресаСтатус
${escapeHtml(nic.location || nic.slot || '-')} ${escapeHtml(nic.model || '-')} ${escapeHtml(nic.vendor || '-')} ${nic.port_count || '-'} ${escapeHtml(nic.port_type || '-')} ${escapeHtml(macs)} ${escapeHtml(nic.status || '-')}
'; } else { html += '

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

'; } html += '
'; // PCIe Device Inventory tab html += '
'; if (config.pcie_devices && config.pcie_devices.length > 0) { html += '

PCIe устройства

'; config.pcie_devices.forEach(p => { html += ``; }); html += '
СлотBDFТипПроизводительVendor:Device IDPCIe Link
${escapeHtml(p.slot || '-')} ${escapeHtml(p.bdf || '-')} ${escapeHtml(p.device_class || '-')} ${escapeHtml(p.manufacturer || '-')} ${p.vendor_id ? p.vendor_id.toString(16) : '-'}:${p.device_id ? p.device_id.toString(16) : '-'} x${p.link_width || '-'} ${escapeHtml(p.link_speed || '-')}
'; } else { html += '

Нет данных о PCIe устройствах

'; } html += '
'; container.innerHTML = html; // Initialize config sub-tabs initConfigTabs(); } function initConfigTabs() { const tabs = document.querySelectorAll('.config-tab'); tabs.forEach(tab => { tab.addEventListener('click', () => { tabs.forEach(t => t.classList.remove('active')); document.querySelectorAll('.config-tab-content').forEach(c => c.classList.remove('active')); tab.classList.add('active'); document.getElementById('config-' + tab.dataset.configTab).classList.add('active'); }); }); } 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; }