ui: embed reanimator chart viewer
This commit is contained in:
@@ -17,6 +17,18 @@ header {
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
.app-header-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 1rem 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.app-header-brand {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
@@ -33,9 +45,34 @@ header p {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.header-log-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.header-actions button {
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 0.55rem 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1400px;
|
||||
margin: 2rem auto;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
margin: 1rem 0 2rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
@@ -109,6 +146,10 @@ main {
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
#data-section {
|
||||
margin: 0 -1rem;
|
||||
}
|
||||
|
||||
.api-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
@@ -437,18 +478,6 @@ main {
|
||||
}
|
||||
|
||||
/* File Info */
|
||||
.file-info {
|
||||
background: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 1rem 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.parser-badge, .file-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -476,6 +505,28 @@ main {
|
||||
border-color: #81c784;
|
||||
}
|
||||
|
||||
.result-panel {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.audit-viewer-shell {
|
||||
min-height: 60vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.audit-viewer-frame {
|
||||
width: 100%;
|
||||
min-height: 60vh;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: #fff;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.tabs {
|
||||
display: flex;
|
||||
|
||||
@@ -5,8 +5,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
initApiSource();
|
||||
initUpload();
|
||||
initConvertMode();
|
||||
initTabs();
|
||||
initFilters();
|
||||
initAuditViewer();
|
||||
loadParsersInfo();
|
||||
loadSupportedFileTypes();
|
||||
});
|
||||
@@ -27,6 +26,30 @@ let isAutoUpdatingApiPort = false;
|
||||
let apiProbeResult = null;
|
||||
let apiPowerDecisionTimer = null;
|
||||
|
||||
function initAuditViewer() {
|
||||
const frame = document.getElementById('audit-viewer-frame');
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
frame.addEventListener('load', () => {
|
||||
resizeAuditViewerFrame();
|
||||
try {
|
||||
const win = frame.contentWindow;
|
||||
if (win) {
|
||||
win.setTimeout(resizeAuditViewerFrame, 50);
|
||||
win.setTimeout(resizeAuditViewerFrame, 250);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to schedule viewer resize:', err);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
resizeAuditViewerFrame();
|
||||
});
|
||||
}
|
||||
|
||||
function initSourceType() {
|
||||
const sourceButtons = document.querySelectorAll('.source-switch-btn');
|
||||
sourceButtons.forEach(button => {
|
||||
@@ -1191,6 +1214,7 @@ let allSerials = [];
|
||||
let allParseErrors = [];
|
||||
|
||||
let currentVendor = '';
|
||||
let auditViewerNonce = 0;
|
||||
|
||||
// Load data from API
|
||||
async function loadData(vendor, filename) {
|
||||
@@ -1198,16 +1222,9 @@ async function loadData(vendor, filename) {
|
||||
document.getElementById('upload-section').classList.add('hidden');
|
||||
document.getElementById('data-section').classList.remove('hidden');
|
||||
document.getElementById('clear-btn').classList.remove('hidden');
|
||||
|
||||
// Update parser name and filename
|
||||
const parserName = document.getElementById('parser-name');
|
||||
const fileNameElem = document.getElementById('file-name');
|
||||
if (parserName && currentVendor) {
|
||||
parserName.textContent = currentVendor;
|
||||
}
|
||||
if (fileNameElem && filename) {
|
||||
fileNameElem.textContent = filename;
|
||||
}
|
||||
document.getElementById('header-raw-btn').classList.remove('hidden');
|
||||
document.getElementById('header-reanimator-btn').classList.remove('hidden');
|
||||
document.getElementById('header-log-meta').classList.remove('hidden');
|
||||
|
||||
// Update vendor badge if exists (legacy support)
|
||||
const vendorBadge = document.getElementById('vendor-badge');
|
||||
@@ -1216,14 +1233,40 @@ async function loadData(vendor, filename) {
|
||||
vendorBadge.classList.remove('hidden');
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
loadConfig(),
|
||||
loadFirmware(),
|
||||
loadSensors(),
|
||||
loadSerials(),
|
||||
loadEvents(),
|
||||
loadParseErrors()
|
||||
]);
|
||||
loadAuditViewer();
|
||||
}
|
||||
|
||||
function loadAuditViewer() {
|
||||
const frame = document.getElementById('audit-viewer-frame');
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
auditViewerNonce += 1;
|
||||
frame.style.height = '60vh';
|
||||
frame.src = `/chart/current?ts=${auditViewerNonce}`;
|
||||
}
|
||||
|
||||
function resizeAuditViewerFrame() {
|
||||
const frame = document.getElementById('audit-viewer-frame');
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const doc = frame.contentDocument || (frame.contentWindow && frame.contentWindow.document);
|
||||
if (!doc || !doc.documentElement || !doc.body) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextHeight = Math.max(
|
||||
doc.documentElement.scrollHeight,
|
||||
doc.body.scrollHeight,
|
||||
640
|
||||
);
|
||||
frame.style.height = `${nextHeight}px`;
|
||||
} catch (err) {
|
||||
console.error('Failed to resize audit viewer frame:', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
@@ -1936,11 +1979,19 @@ async function clearData() {
|
||||
document.getElementById('upload-section').classList.remove('hidden');
|
||||
document.getElementById('data-section').classList.add('hidden');
|
||||
document.getElementById('clear-btn').classList.add('hidden');
|
||||
document.getElementById('header-raw-btn').classList.add('hidden');
|
||||
document.getElementById('header-reanimator-btn').classList.add('hidden');
|
||||
document.getElementById('header-log-meta').classList.add('hidden');
|
||||
document.getElementById('upload-status').textContent = '';
|
||||
allSensors = [];
|
||||
allEvents = [];
|
||||
allSerials = [];
|
||||
allParseErrors = [];
|
||||
currentVendor = '';
|
||||
const frame = document.getElementById('audit-viewer-frame');
|
||||
if (frame) {
|
||||
frame.src = 'about:blank';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to clear data:', err);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,21 @@
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>LOGPile <span class="header-domain">mchus.pro</span></h1>
|
||||
<p>Анализатор диагностических данных BMC/IPMI</p>
|
||||
<div class="app-header-row">
|
||||
<div class="app-header-brand">
|
||||
<h1>LOGPile <span class="header-domain">mchus.pro</span></h1>
|
||||
<p>Анализатор диагностических данных BMC/IPMI</p>
|
||||
</div>
|
||||
<div id="header-log-meta" class="header-log-meta hidden">
|
||||
<div class="header-actions">
|
||||
<button id="clear-btn" class="hidden" onclick="clearData()">Очистить данные</button>
|
||||
<button id="header-raw-btn" class="hidden" onclick="exportData('json')">Export Raw Data</button>
|
||||
<button id="header-reanimator-btn" class="hidden" onclick="exportData('reanimator')">Экспорт Reanimator</button>
|
||||
<button id="restart-btn" onclick="restartApp()">Перезапуск</button>
|
||||
<button id="exit-btn" onclick="exitApp()">Выход</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
@@ -124,142 +137,23 @@
|
||||
</section>
|
||||
|
||||
<section id="data-section" class="hidden">
|
||||
<div class="file-info">
|
||||
<div class="parser-badge">
|
||||
<span class="badge-label">Парсер:</span>
|
||||
<span id="parser-name" class="badge-value"></span>
|
||||
<section class="result-panel">
|
||||
<div class="audit-viewer-shell">
|
||||
<iframe
|
||||
id="audit-viewer-frame"
|
||||
class="audit-viewer-frame"
|
||||
title="Reanimator chart viewer"
|
||||
loading="eager"
|
||||
scrolling="no"
|
||||
referrerpolicy="same-origin">
|
||||
</iframe>
|
||||
</div>
|
||||
<div class="file-name">
|
||||
<span class="badge-label">Файл:</span>
|
||||
<span id="file-name" class="badge-value"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="tabs">
|
||||
<button class="tab active" data-tab="config">Конфигурация</button>
|
||||
<button class="tab" data-tab="firmware">Прошивки</button>
|
||||
<button class="tab" data-tab="sensors">Сенсоры</button>
|
||||
<button class="tab" data-tab="serials">Серийные номера</button>
|
||||
<button class="tab" data-tab="events">События</button>
|
||||
<button class="tab" data-tab="parse-errors">Ошибки разбора</button>
|
||||
</nav>
|
||||
|
||||
<div class="tab-content active" id="config">
|
||||
<div class="toolbar">
|
||||
<button onclick="exportData('json')">Export Raw Data</button>
|
||||
<button onclick="exportData('reanimator')">Экспорт Reanimator</button>
|
||||
</div>
|
||||
<div id="config-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="firmware">
|
||||
<div class="toolbar">
|
||||
<span class="toolbar-label">Версии прошивок компонентов</span>
|
||||
</div>
|
||||
<table id="firmware-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Компонент</th>
|
||||
<th>Модель</th>
|
||||
<th>Версия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="sensors">
|
||||
<div class="toolbar">
|
||||
<select id="sensor-filter">
|
||||
<option value="">Все сенсоры</option>
|
||||
<option value="temperature">Температура</option>
|
||||
<option value="voltage">Напряжение</option>
|
||||
<option value="power">Мощность</option>
|
||||
<option value="fan_speed">Вентиляторы</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="sensors-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="serials">
|
||||
<div class="toolbar">
|
||||
<select id="serial-filter">
|
||||
<option value="">Все компоненты</option>
|
||||
<option value="Board">Материнская плата</option>
|
||||
<option value="CPU">Процессоры</option>
|
||||
<option value="Memory">Память</option>
|
||||
<option value="Storage">Накопители</option>
|
||||
<option value="PCIe">PCIe устройства</option>
|
||||
<option value="Network">Сетевые адаптеры</option>
|
||||
<option value="PSU">Блоки питания</option>
|
||||
<option value="Firmware">Прошивки</option>
|
||||
<option value="FRU">FRU</option>
|
||||
</select>
|
||||
<button onclick="exportData('csv')">Экспорт CSV</button>
|
||||
</div>
|
||||
<table id="serials-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Категория</th>
|
||||
<th>Компонент</th>
|
||||
<th>Расположение</th>
|
||||
<th>Серийный номер</th>
|
||||
<th>Производитель</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="events">
|
||||
<div class="toolbar">
|
||||
<select id="severity-filter">
|
||||
<option value="">Все события</option>
|
||||
<option value="critical">Критические</option>
|
||||
<option value="warning">Предупреждения</option>
|
||||
<option value="info">Информационные</option>
|
||||
</select>
|
||||
</div>
|
||||
<table id="events-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Время</th>
|
||||
<th>Источник</th>
|
||||
<th>Описание</th>
|
||||
<th>Важность</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="parse-errors">
|
||||
<div class="toolbar">
|
||||
<span class="toolbar-label">Ошибки сборки / разбора (Redfish, parser, файл)</span>
|
||||
</div>
|
||||
<div class="table-scroll">
|
||||
<table id="parse-errors-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Источник</th>
|
||||
<th>Категория</th>
|
||||
<th>Важность</th>
|
||||
<th>Endpoint / Path</th>
|
||||
<th>Сообщение</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-buttons">
|
||||
<button id="clear-btn" class="hidden" onclick="clearData()">Очистить данные</button>
|
||||
<button id="restart-btn" onclick="restartApp()">Перезапуск</button>
|
||||
<button id="exit-btn" onclick="exitApp()">Выход</button>
|
||||
</div>
|
||||
<div class="footer-info">
|
||||
<p>Автор: <a href="https://mchus.pro" target="_blank">mchus.pro</a> | <a href="https://git.mchus.pro/mchus/logpile" target="_blank">Git Repository</a></p>
|
||||
|
||||
Reference in New Issue
Block a user