diff --git a/web/static/js/app.js b/web/static/js/app.js index 200070b..fcfb301 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -523,14 +523,61 @@ function appendJobLog(message) { return; } - const time = new Date().toLocaleTimeString('ru-RU', { hour12: false }); + const parsed = parseServerLogLine(message); + if (isCollectLogNoise(parsed.message)) { + // Still count toward log length so syncServerLogs offset stays correct, + // but mark as hidden so renderCollectionJob skips it. + collectionJob.logs.push({ + id: ++collectionJobLogCounter, + time: parsed.time || new Date().toLocaleTimeString('ru-RU', { hour12: false }), + message: parsed.message, + hidden: true + }); + return; + } + collectionJob.logs.push({ id: ++collectionJobLogCounter, - time, - message + time: parsed.time || new Date().toLocaleTimeString('ru-RU', { hour12: false }), + message: humanizeCollectLogMessage(parsed.message) }); } +// Transform technical log messages into human-readable form for the UI. +// The original messages are preserved in collect.log / raw_export. +function humanizeCollectLogMessage(msg) { + // "Redfish snapshot: документов=520, ETA≈16s, корни=Chassis(294), Systems(114), последний=/redfish/v1/..." + // → "Snapshot: /Chassis/Self/PCIeDevices/00_34_04" + let m = msg.match(/snapshot:\s+документов=\d+[^,]*,.*последний=(\S+)/i); + if (m) { + const path = m[1].replace(/^\.\.\./, '').replace(/^\/redfish\/v1/, '') || m[1]; + return `Snapshot: ${path}`; + } + + // "Redfish snapshot: собрано N документов" + m = msg.match(/snapshot:\s+собрано\s+(\d+)\s+документов/i); + if (m) { + return `Snapshot: итого ${m[1]} документов`; + } + + // "Redfish: plan-B завершен за 30s (targets=18, recovered=0)" + m = msg.match(/plan-B завершен за ([^(]+)\(targets=(\d+),\s*recovered=(\d+)\)/i); + if (m) { + const recovered = parseInt(m[3], 10); + const suffix = recovered > 0 ? `, восстановлено ${m[3]}` : ''; + return `Plan-B: завершен за ${m[1].trim()}${suffix}`; + } + + // "Redfish: prefetch критичных endpoint (адаптивно 9/72)..." + m = msg.match(/prefetch критичных endpoint[^(]*\(([^)]+)\)/i); + if (m) { + return `Prefetch критичных endpoint (${m[1]})`; + } + + // Strip "Redfish: " / "Redfish snapshot: " prefix — redundant in context + return msg.replace(/^Redfish(?:\s+snapshot)?:\s+/i, ''); +} + function renderCollectionJob() { const jobStatusBlock = document.getElementById('api-job-status'); const jobIdValue = document.getElementById('job-id-value'); @@ -576,9 +623,11 @@ function renderCollectionJob() { renderJobActiveModules(activeModulesBlock, activeModulesList); renderJobDebugInfo(debugInfoBlock, debugSummary, phaseTelemetryNode); - logsList.innerHTML = [...collectionJob.logs].reverse().map((log) => ( - `
  • ${escapeHtml(log.time)}${escapeHtml(log.message)}
  • ` - )).join(''); + logsList.innerHTML = [...collectionJob.logs].reverse() + .filter((log) => !log.hidden) + .map((log) => ( + `
  • ${escapeHtml(log.time)}${escapeHtml(log.message)}
  • ` + )).join(''); cancelButton.disabled = isTerminal; setApiFormBlocked(!isTerminal); @@ -668,6 +717,38 @@ function syncServerLogs(logs) { } } +// Patterns for log lines that are internal debug noise and should not be shown in the UI. +const _collectLogNoisePatterns = [ + /plan-B \(\d+\/\d+/, // individual plan-B step lines + /plan-B топ веток/, + /snapshot: heartbeat/, + /snapshot: post-probe коллекций \(/, + /snapshot: топ веток/, + /prefetch завершен/, + /cooldown перед повторным добором/, + /Redfish telemetry:/, + /redfish-postprobe-metrics:/, + /redfish-prefetch-metrics:/, + /redfish-collect:/, + /redfish-profile-plan:/, + /redfish replay:/, +]; + +function isCollectLogNoise(message) { + return _collectLogNoisePatterns.some((re) => re.test(message)); +} + +// Strip the server-side RFC3339Nano timestamp prefix from a log line and return {time, message}. +function parseServerLogLine(raw) { + const m = String(raw).match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)\s+(.*)/s); + if (!m) { + return { time: null, message: String(raw).trim() }; + } + const d = new Date(m[1]); + const time = isNaN(d) ? null : d.toLocaleTimeString('ru-RU', { hour12: false }); + return { time, message: m[2].trim() }; +} + function normalizeJobStatus(status) { return String(status || '').trim().toLowerCase(); }