mirror of
https://github.com/dockpeek/dockpeek.git
synced 2026-05-12 19:38:42 -05:00
ANSI escape codes
This commit is contained in:
@@ -1990,4 +1990,22 @@ body {
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@keyframes ansi-blink {
|
||||
|
||||
0%,
|
||||
49% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ansi-blink {
|
||||
animation: ansi-blink 1s linear infinite;
|
||||
}
|
||||
@@ -608,6 +608,15 @@
|
||||
.text-white {
|
||||
color: var(--color-white);
|
||||
}
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.line-through {
|
||||
text-decoration-line: line-through;
|
||||
}
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
.opacity-25 {
|
||||
opacity: 25%;
|
||||
}
|
||||
@@ -618,6 +627,10 @@
|
||||
--tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.grayscale {
|
||||
--tw-grayscale: grayscale(100%);
|
||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||
}
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
@@ -2768,6 +2781,17 @@ body {
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@keyframes ansi-blink {
|
||||
0%, 49% {
|
||||
opacity: 1;
|
||||
}
|
||||
50%, 100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.ansi-blink {
|
||||
animation: ansi-blink 1s linear infinite;
|
||||
}
|
||||
@property --tw-rotate-x {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -2876,6 +2900,59 @@ body {
|
||||
inherits: false;
|
||||
initial-value: 0 0 #0000;
|
||||
}
|
||||
@property --tw-blur {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-brightness {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-contrast {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-grayscale {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-hue-rotate {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-invert {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-opacity {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-saturate {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-sepia {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-drop-shadow {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-drop-shadow-color {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-drop-shadow-alpha {
|
||||
syntax: "<percentage>";
|
||||
inherits: false;
|
||||
initial-value: 100%;
|
||||
}
|
||||
@property --tw-drop-shadow-size {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-duration {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -2916,6 +2993,19 @@ body {
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-blur: initial;
|
||||
--tw-brightness: initial;
|
||||
--tw-contrast: initial;
|
||||
--tw-grayscale: initial;
|
||||
--tw-hue-rotate: initial;
|
||||
--tw-invert: initial;
|
||||
--tw-opacity: initial;
|
||||
--tw-saturate: initial;
|
||||
--tw-sepia: initial;
|
||||
--tw-drop-shadow: initial;
|
||||
--tw-drop-shadow-color: initial;
|
||||
--tw-drop-shadow-alpha: 100%;
|
||||
--tw-drop-shadow-size: initial;
|
||||
--tw-duration: initial;
|
||||
--tw-ease: initial;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
|
||||
export class AnsiParser {
|
||||
constructor() {
|
||||
this.colors = {
|
||||
30: '#000000',
|
||||
31: '#CD3131',
|
||||
32: '#0DBC79',
|
||||
33: '#E5E510',
|
||||
34: '#2472C8',
|
||||
35: '#BC3FBC',
|
||||
36: '#11A8CD',
|
||||
37: '#E5E5E5',
|
||||
|
||||
90: '#666666',
|
||||
91: '#F14C4C',
|
||||
92: '#23D18B',
|
||||
93: '#F5F543',
|
||||
94: '#3B8EEA',
|
||||
95: '#D670D6',
|
||||
96: '#29B8DB',
|
||||
97: '#FFFFFF',
|
||||
|
||||
40: '#000000',
|
||||
41: '#CD3131',
|
||||
42: '#0DBC79',
|
||||
43: '#E5E510',
|
||||
44: '#2472C8',
|
||||
45: '#BC3FBC',
|
||||
46: '#11A8CD',
|
||||
47: '#E5E5E5',
|
||||
|
||||
100: '#666666',
|
||||
101: '#F14C4C',
|
||||
102: '#23D18B',
|
||||
103: '#F5F543',
|
||||
104: '#3B8EEA',
|
||||
105: '#D670D6',
|
||||
106: '#29B8DB',
|
||||
107: '#FFFFFF'
|
||||
};
|
||||
}
|
||||
|
||||
parse(text) {
|
||||
const ansiRegex = /\x1b\[([0-9;]*)m/g;
|
||||
|
||||
const segments = [];
|
||||
let lastIndex = 0;
|
||||
let currentStyle = this.createEmptyStyle();
|
||||
|
||||
let match;
|
||||
while ((match = ansiRegex.exec(text)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
const textSegment = text.substring(lastIndex, match.index);
|
||||
segments.push({
|
||||
text: textSegment,
|
||||
style: { ...currentStyle }
|
||||
});
|
||||
}
|
||||
|
||||
const codes = match[1] ? match[1].split(';').map(Number) : [0];
|
||||
currentStyle = this.applyCodes(currentStyle, codes);
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
}
|
||||
|
||||
if (lastIndex < text.length) {
|
||||
segments.push({
|
||||
text: text.substring(lastIndex),
|
||||
style: { ...currentStyle }
|
||||
});
|
||||
}
|
||||
|
||||
if (segments.length === 0) {
|
||||
segments.push({
|
||||
text: text,
|
||||
style: this.createEmptyStyle()
|
||||
});
|
||||
}
|
||||
|
||||
return this.segmentsToHtml(segments);
|
||||
}
|
||||
|
||||
createEmptyStyle() {
|
||||
return {
|
||||
color: null,
|
||||
background: null,
|
||||
bold: false,
|
||||
dim: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
blink: false,
|
||||
reverse: false,
|
||||
hidden: false,
|
||||
strikethrough: false
|
||||
};
|
||||
}
|
||||
|
||||
applyCodes(style, codes) {
|
||||
const newStyle = { ...style };
|
||||
|
||||
let i = 0;
|
||||
while (i < codes.length) {
|
||||
const code = codes[i];
|
||||
|
||||
switch (code) {
|
||||
case 0:
|
||||
return this.createEmptyStyle();
|
||||
|
||||
case 1:
|
||||
newStyle.bold = true;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
newStyle.dim = true;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
newStyle.italic = true;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
newStyle.underline = true;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
newStyle.blink = true;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
newStyle.reverse = true;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
newStyle.hidden = true;
|
||||
break;
|
||||
|
||||
case 9:
|
||||
newStyle.strikethrough = true;
|
||||
break;
|
||||
|
||||
case 22:
|
||||
newStyle.bold = false;
|
||||
newStyle.dim = false;
|
||||
break;
|
||||
|
||||
case 23:
|
||||
newStyle.italic = false;
|
||||
break;
|
||||
|
||||
case 24:
|
||||
newStyle.underline = false;
|
||||
break;
|
||||
|
||||
case 25:
|
||||
newStyle.blink = false;
|
||||
break;
|
||||
|
||||
case 27:
|
||||
newStyle.reverse = false;
|
||||
break;
|
||||
|
||||
case 28:
|
||||
newStyle.hidden = false;
|
||||
break;
|
||||
|
||||
case 29:
|
||||
newStyle.strikethrough = false;
|
||||
break;
|
||||
|
||||
case 39:
|
||||
newStyle.color = null;
|
||||
break;
|
||||
|
||||
case 49:
|
||||
newStyle.background = null;
|
||||
break;
|
||||
|
||||
case 38:
|
||||
if (codes[i + 1] === 5 && codes[i + 2] !== undefined) {
|
||||
newStyle.color = this.get256Color(codes[i + 2]);
|
||||
i += 2;
|
||||
} else if (codes[i + 1] === 2 && codes[i + 4] !== undefined) {
|
||||
newStyle.color = `rgb(${codes[i + 2]}, ${codes[i + 3]}, ${codes[i + 4]})`;
|
||||
i += 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case 48:
|
||||
if (codes[i + 1] === 5 && codes[i + 2] !== undefined) {
|
||||
newStyle.background = this.get256Color(codes[i + 2]);
|
||||
i += 2;
|
||||
} else if (codes[i + 1] === 2 && codes[i + 4] !== undefined) {
|
||||
newStyle.background = `rgb(${codes[i + 2]}, ${codes[i + 3]}, ${codes[i + 4]})`;
|
||||
i += 4;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (this.colors[code]) {
|
||||
if (code >= 30 && code <= 37) {
|
||||
newStyle.color = this.colors[code];
|
||||
} else if (code >= 40 && code <= 47) {
|
||||
newStyle.background = this.colors[code];
|
||||
} else if (code >= 90 && code <= 97) {
|
||||
newStyle.color = this.colors[code];
|
||||
} else if (code >= 100 && code <= 107) {
|
||||
newStyle.background = this.colors[code];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return newStyle;
|
||||
}
|
||||
|
||||
get256Color(code) {
|
||||
if (code < 16) {
|
||||
const mapping = [30, 31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97];
|
||||
return this.colors[mapping[code]];
|
||||
}
|
||||
|
||||
if (code >= 16 && code <= 231) {
|
||||
const index = code - 16;
|
||||
const r = Math.floor(index / 36);
|
||||
const g = Math.floor((index % 36) / 6);
|
||||
const b = index % 6;
|
||||
|
||||
const toRgb = (v) => v === 0 ? 0 : 55 + v * 40;
|
||||
|
||||
return `rgb(${toRgb(r)}, ${toRgb(g)}, ${toRgb(b)})`;
|
||||
}
|
||||
|
||||
if (code >= 232 && code <= 255) {
|
||||
const gray = 8 + (code - 232) * 10;
|
||||
return `rgb(${gray}, ${gray}, ${gray})`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
segmentsToHtml(segments, additionalColorizer = null) {
|
||||
return segments.map(segment => {
|
||||
if (!segment.text) return '';
|
||||
|
||||
const style = segment.style;
|
||||
const hasStyle = style.color || style.background || style.bold ||
|
||||
style.dim || style.italic || style.underline ||
|
||||
style.strikethrough || style.reverse || style.hidden;
|
||||
|
||||
|
||||
let escapedText = this.escapeHtml(segment.text);
|
||||
|
||||
if (additionalColorizer) {
|
||||
escapedText = additionalColorizer(escapedText, segment.text);
|
||||
}
|
||||
|
||||
if (!hasStyle) {
|
||||
return escapedText;
|
||||
}
|
||||
|
||||
const styles = [];
|
||||
const classes = [];
|
||||
|
||||
let color = style.color;
|
||||
let background = style.background;
|
||||
|
||||
if (style.reverse) {
|
||||
[color, background] = [background || '#E5E5E5', color || '#000000'];
|
||||
}
|
||||
|
||||
if (color) styles.push(`color: ${color}`);
|
||||
if (background) styles.push(`background-color: ${background}`);
|
||||
|
||||
if (style.bold) {
|
||||
styles.push('font-weight: bold');
|
||||
}
|
||||
|
||||
if (style.dim) {
|
||||
styles.push('opacity: 0.6');
|
||||
}
|
||||
|
||||
if (style.italic) {
|
||||
styles.push('font-style: italic');
|
||||
}
|
||||
|
||||
if (style.underline) {
|
||||
styles.push('text-decoration: underline');
|
||||
}
|
||||
|
||||
if (style.strikethrough) {
|
||||
styles.push('text-decoration: line-through');
|
||||
}
|
||||
|
||||
if (style.blink) {
|
||||
classes.push('ansi-blink');
|
||||
}
|
||||
|
||||
if (style.hidden) {
|
||||
styles.push('visibility: hidden');
|
||||
}
|
||||
|
||||
const classAttr = classes.length > 0 ? ` class="${classes.join(' ')}"` : '';
|
||||
const styleAttr = styles.length > 0 ? ` style="${styles.join('; ')}"` : '';
|
||||
|
||||
return `<span${classAttr}${styleAttr}>${escapedText}</span>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
parseWithColorizer(text, colorizer) {
|
||||
const ansiRegex = /\x1b\[([0-9;]*)m/g;
|
||||
|
||||
const segments = [];
|
||||
let lastIndex = 0;
|
||||
let currentStyle = this.createEmptyStyle();
|
||||
|
||||
let match;
|
||||
while ((match = ansiRegex.exec(text)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
const textSegment = text.substring(lastIndex, match.index);
|
||||
segments.push({
|
||||
text: textSegment,
|
||||
style: { ...currentStyle }
|
||||
});
|
||||
}
|
||||
|
||||
const codes = match[1] ? match[1].split(';').map(Number) : [0];
|
||||
currentStyle = this.applyCodes(currentStyle, codes);
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
}
|
||||
|
||||
if (lastIndex < text.length) {
|
||||
segments.push({
|
||||
text: text.substring(lastIndex),
|
||||
style: { ...currentStyle }
|
||||
});
|
||||
}
|
||||
|
||||
if (segments.length === 0) {
|
||||
segments.push({
|
||||
text: text,
|
||||
style: this.createEmptyStyle()
|
||||
});
|
||||
}
|
||||
|
||||
return this.segmentsToHtml(segments, colorizer);
|
||||
}
|
||||
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
static stripAnsi(text) {
|
||||
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
export const ansiParser = new AnsiParser();
|
||||
|
||||
export default AnsiParser;
|
||||
@@ -1,5 +1,7 @@
|
||||
// modules/logs-viewer.js
|
||||
|
||||
import { apiUrl } from './config.js';
|
||||
import { ansiParser } from './ansi-parser.js';
|
||||
|
||||
export class LogsViewer {
|
||||
constructor() {
|
||||
this.modal = null;
|
||||
@@ -276,7 +278,7 @@ export class LogsViewer {
|
||||
}
|
||||
});
|
||||
|
||||
// Close on overlay click
|
||||
|
||||
this.modal.addEventListener('click', (e) => {
|
||||
if (e.target === this.modal) this.close();
|
||||
});
|
||||
@@ -292,7 +294,7 @@ export class LogsViewer {
|
||||
this.navigateToContainer(1);
|
||||
}
|
||||
});
|
||||
// Close on Escape key
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && !this.modal.classList.contains('hidden')) {
|
||||
this.close();
|
||||
@@ -449,7 +451,7 @@ export class LogsViewer {
|
||||
let timestampPart = '';
|
||||
let contentPart = line;
|
||||
|
||||
// Parse timestamp first
|
||||
|
||||
const timestampRegex = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z)\s+(.*)$/;
|
||||
const timestampMatch = line.match(timestampRegex);
|
||||
|
||||
@@ -458,7 +460,7 @@ export class LogsViewer {
|
||||
contentPart = timestampMatch[2];
|
||||
}
|
||||
|
||||
// Parse Swarm task details from content
|
||||
|
||||
const swarmDetailsRegex = /^com\.docker\.swarm\.[^\s]+ (.*)$/;
|
||||
const swarmMatch = contentPart.match(swarmDetailsRegex);
|
||||
|
||||
@@ -497,10 +499,13 @@ export class LogsViewer {
|
||||
}
|
||||
|
||||
colorizeLogLine(line) {
|
||||
const urls = [];
|
||||
const urlPlaceholder = '___URL_PLACEHOLDER_';
|
||||
if (!line.trim()) return '';
|
||||
|
||||
let processedLine = line.replace(
|
||||
return ansiParser.parseWithColorizer(line, (escapedHtml, originalText) => {
|
||||
const urls = [];
|
||||
const urlPlaceholder = '___URL___';
|
||||
|
||||
escapedHtml = escapedHtml.replace(
|
||||
/https?:\/\/[^\s"'<>|\\{}[\]`]+/g,
|
||||
(match) => {
|
||||
const cleaned = match.replace(/[.,;:!?)}"':]+$/, '');
|
||||
@@ -508,46 +513,44 @@ export class LogsViewer {
|
||||
return `${urlPlaceholder}${urls.length - 1}${urlPlaceholder}`;
|
||||
}
|
||||
);
|
||||
|
||||
let escapedLine = this.escapeHtml(processedLine);
|
||||
|
||||
escapedLine = escapedLine.replace(/\d+/g, (match, offset) => {
|
||||
const before = escapedLine.substring(Math.max(0, offset - urlPlaceholder.length), offset);
|
||||
const after = escapedLine.substring(offset + match.length, offset + match.length + urlPlaceholder.length);
|
||||
|
||||
|
||||
escapedHtml = escapedHtml.replace(/(\d+)/g, (match, num, offset) => {
|
||||
const before = escapedHtml.substring(Math.max(0, offset - urlPlaceholder.length), offset);
|
||||
const after = escapedHtml.substring(offset + match.length, offset + match.length + urlPlaceholder.length);
|
||||
|
||||
if (before === urlPlaceholder && after === urlPlaceholder) {
|
||||
return match;
|
||||
}
|
||||
return `<span class="log-number">${match}</span>`;
|
||||
});
|
||||
|
||||
escapedLine = escapedLine.replace(
|
||||
|
||||
escapedHtml = escapedHtml.replace(
|
||||
new RegExp(`${urlPlaceholder}(\\d+)${urlPlaceholder}`, 'g'),
|
||||
(match, index) => {
|
||||
const url = urls[parseInt(index)];
|
||||
const escapedUrl = this.escapeHtml(url);
|
||||
return `<a href="${escapedUrl}" target="_blank" rel="noopener noreferrer" class="log-link">${escapedUrl}</a>`;
|
||||
return `<a href="${url}" target="_blank" rel="noopener noreferrer" class="log-link">${url}</a>`;
|
||||
}
|
||||
);
|
||||
|
||||
if (/\b(ERROR|ERR|ERRO)\b|\[(ERROR|ERR|ERRO)\]/i.test(escapedLine)) {
|
||||
return `<span class="log-error">${escapedLine}</span>`;
|
||||
|
||||
if (/\b(ERROR|ERR|ERRO)\b|\[(ERROR|ERR|ERRO)\]/i.test(originalText)) {
|
||||
return `<span class="log-error">${escapedHtml}</span>`;
|
||||
}
|
||||
if (/\b(WARN|WARNING)\b|\[(WARN|WARNING)\]/i.test(escapedLine)) {
|
||||
return `<span class="log-warning">${escapedLine}</span>`;
|
||||
if (/\b(WARN|WARNING)\b|\[(WARN|WARNING)\]/i.test(originalText)) {
|
||||
return `<span class="log-warning">${escapedHtml}</span>`;
|
||||
}
|
||||
if (/\b(INFO)\b|\[(INFO)\]/i.test(escapedLine)) {
|
||||
return `<span class="log-info">${escapedLine}</span>`;
|
||||
if (/\b(INFO)\b|\[(INFO)\]/i.test(originalText)) {
|
||||
return `<span class="log-info">${escapedHtml}</span>`;
|
||||
}
|
||||
if (/\b(DEBUG|TRACE)\b|\[(DEBUG|TRACE)\]/i.test(escapedLine)) {
|
||||
return `<span class="log-debug">${escapedLine}</span>`;
|
||||
if (/\b(DEBUG|TRACE)\b|\[(DEBUG|TRACE)\]/i.test(originalText)) {
|
||||
return `<span class="log-debug">${escapedHtml}</span>`;
|
||||
}
|
||||
if (/\b(SUCCESS|OK|DONE|READY)\b|\[(SUCCESS|OK|DONE|READY)\]/i.test(escapedLine)) {
|
||||
return `<span class="log-success">${escapedLine}</span>`;
|
||||
if (/\b(SUCCESS|OK|DONE|READY)\b|\[(SUCCESS|OK|DONE|READY)\]/i.test(originalText)) {
|
||||
return `<span class="log-success">${escapedHtml}</span>`;
|
||||
}
|
||||
|
||||
return escapedLine;
|
||||
}
|
||||
|
||||
return escapedHtml;
|
||||
});
|
||||
}
|
||||
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
@@ -656,19 +659,19 @@ export class LogsViewer {
|
||||
appendLogLine(line) {
|
||||
let pre = this.logsContent.querySelector('.logs-pre');
|
||||
|
||||
// Jeśli nie ma pre, utwórz go
|
||||
|
||||
if (!pre) {
|
||||
this.logsContent.innerHTML = '<pre class="logs-pre"></pre>';
|
||||
pre = this.logsContent.querySelector('.logs-pre');
|
||||
}
|
||||
|
||||
if (pre) {
|
||||
// Usuń trailing newline z linii
|
||||
|
||||
const cleanLine = line.replace(/\n$/, '');
|
||||
const formattedLine = this.formatLogLine(cleanLine);
|
||||
pre.insertAdjacentHTML('beforeend', formattedLine);
|
||||
|
||||
// Ogranicz liczbę linii w pamięci
|
||||
|
||||
const lines = pre.querySelectorAll('.log-line');
|
||||
if (lines.length > 5000) {
|
||||
lines[0].remove();
|
||||
@@ -732,7 +735,7 @@ export class LogsViewer {
|
||||
const prevBtn = document.getElementById('logs-search-prev');
|
||||
const nextBtn = document.getElementById('logs-search-next');
|
||||
|
||||
// Reset
|
||||
|
||||
this.searchMatches = [];
|
||||
this.currentMatchIndex = -1;
|
||||
|
||||
@@ -747,7 +750,7 @@ export class LogsViewer {
|
||||
|
||||
clearBtn.classList.remove('hidden');
|
||||
|
||||
// Find all matches
|
||||
|
||||
const lines = this.logsContent.querySelectorAll('.log-line');
|
||||
const lowerQuery = query.toLowerCase();
|
||||
|
||||
@@ -771,7 +774,7 @@ export class LogsViewer {
|
||||
}
|
||||
});
|
||||
|
||||
// Update UI
|
||||
|
||||
if (this.searchMatches.length > 0) {
|
||||
countSpan.classList.remove('hidden');
|
||||
prevBtn.classList.remove('hidden');
|
||||
@@ -891,13 +894,13 @@ export class LogsViewer {
|
||||
}
|
||||
|
||||
updateActiveHighlight() {
|
||||
// Remove all active highlights
|
||||
|
||||
this.logsContent.querySelectorAll('mark.search-highlight-active').forEach(el => {
|
||||
el.classList.remove('search-highlight-active');
|
||||
el.classList.add('search-highlight');
|
||||
});
|
||||
|
||||
// Add active highlight to current match
|
||||
|
||||
const marks = this.logsContent.querySelectorAll('mark[data-match-index]');
|
||||
marks.forEach(mark => {
|
||||
const idx = parseInt(mark.dataset.matchIndex);
|
||||
@@ -956,5 +959,5 @@ export class LogsViewer {
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
|
||||
export const logsViewer = new LogsViewer();
|
||||
Reference in New Issue
Block a user