158 lines
5.7 KiB
JavaScript
158 lines
5.7 KiB
JavaScript
// Filename: frontend/js/pages/logs/systemLog.js
|
|
|
|
export default class SystemLogTerminal {
|
|
constructor(container, controlsContainer) {
|
|
this.container = container;
|
|
this.controlsContainer = controlsContainer;
|
|
this.ws = null;
|
|
this.isPaused = false;
|
|
this.shouldAutoScroll = true;
|
|
this.reconnectAttempts = 0;
|
|
this.maxReconnectAttempts = 5;
|
|
|
|
this.elements = {
|
|
output: this.container.querySelector('#log-terminal-output'),
|
|
statusIndicator: this.controlsContainer.querySelector('#terminal-status-indicator'),
|
|
clearBtn: this.controlsContainer.querySelector('[data-action="clear-terminal"]'),
|
|
pauseBtn: this.controlsContainer.querySelector('[data-action="toggle-pause-terminal"]'),
|
|
scrollBtn: this.controlsContainer.querySelector('[data-action="toggle-scroll-terminal"]'),
|
|
disconnectBtn: this.controlsContainer.querySelector('[data-action="disconnect-terminal"]'),
|
|
};
|
|
|
|
this._initEventListeners();
|
|
}
|
|
|
|
_initEventListeners() {
|
|
this.elements.clearBtn.addEventListener('click', () => this.clear());
|
|
this.elements.pauseBtn.addEventListener('click', () => this.togglePause());
|
|
this.elements.scrollBtn.addEventListener('click', () => this.toggleAutoScroll());
|
|
this.elements.disconnectBtn.addEventListener('click', () => this.disconnect());
|
|
}
|
|
|
|
connect() {
|
|
this.clear();
|
|
this._appendMessage('info', '正在连接到实时日志流...');
|
|
this._updateStatus('connecting', '连接中...');
|
|
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const wsUrl = `${protocol}//${window.location.host}/ws/system-logs`;
|
|
|
|
this.ws = new WebSocket(wsUrl);
|
|
|
|
this.ws.onopen = () => {
|
|
this._appendMessage('info', '✓ 已连接到系统日志流');
|
|
this._updateStatus('connected', '已连接');
|
|
this.reconnectAttempts = 0;
|
|
};
|
|
|
|
this.ws.onmessage = (event) => {
|
|
if (this.isPaused) return;
|
|
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
const levelColors = {
|
|
'error': 'text-red-500',
|
|
'warning': 'text-yellow-400',
|
|
'info': 'text-blue-400',
|
|
'debug': 'text-zinc-400'
|
|
};
|
|
const color = levelColors[data.level] || 'text-zinc-200';
|
|
const timestamp = new Date(data.timestamp).toLocaleTimeString();
|
|
const msg = `[${timestamp}] [${data.level.toUpperCase()}] ${data.message}`;
|
|
this._appendMessage(color, msg);
|
|
} catch (e) {
|
|
this._appendMessage('text-zinc-200', event.data);
|
|
}
|
|
};
|
|
|
|
this.ws.onerror = (error) => {
|
|
this._appendMessage('error', `✗ WebSocket 错误`);
|
|
this._updateStatus('error', '连接错误');
|
|
};
|
|
|
|
this.ws.onclose = () => {
|
|
this._appendMessage('error', '✗ 连接已断开');
|
|
this._updateStatus('disconnected', '未连接');
|
|
|
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
this.reconnectAttempts++;
|
|
setTimeout(() => {
|
|
this._appendMessage('info', `尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
|
this.connect();
|
|
}, 3000);
|
|
}
|
|
};
|
|
}
|
|
|
|
disconnect() {
|
|
if (this.ws) {
|
|
this.ws.close();
|
|
this.ws = null;
|
|
}
|
|
this.reconnectAttempts = this.maxReconnectAttempts;
|
|
this._updateStatus('disconnected', '未连接');
|
|
}
|
|
|
|
clear() {
|
|
if(this.elements.output) {
|
|
this.elements.output.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
togglePause() {
|
|
this.isPaused = !this.isPaused;
|
|
const span = this.elements.pauseBtn.querySelector('span');
|
|
const icon = this.elements.pauseBtn.querySelector('i');
|
|
if (this.isPaused) {
|
|
span.textContent = '继续';
|
|
icon.classList.replace('fa-pause', 'fa-play');
|
|
} else {
|
|
span.textContent = '暂停';
|
|
icon.classList.replace('fa-play', 'fa-pause');
|
|
}
|
|
}
|
|
|
|
toggleAutoScroll() {
|
|
this.shouldAutoScroll = !this.shouldAutoScroll;
|
|
const span = this.elements.scrollBtn.querySelector('span');
|
|
if (this.shouldAutoScroll) {
|
|
span.textContent = '自动滚动';
|
|
} else {
|
|
span.textContent = '手动滚动';
|
|
}
|
|
}
|
|
|
|
_appendMessage(colorClass, text) {
|
|
if (!this.elements.output) return;
|
|
|
|
const p = document.createElement('p');
|
|
p.className = colorClass;
|
|
p.textContent = text;
|
|
this.elements.output.appendChild(p);
|
|
|
|
if (this.shouldAutoScroll) {
|
|
this.elements.output.scrollTop = this.elements.output.scrollHeight;
|
|
}
|
|
}
|
|
|
|
_updateStatus(status, text) {
|
|
const indicator = this.elements.statusIndicator.querySelector('span.relative');
|
|
const statusText = this.elements.statusIndicator.childNodes[2];
|
|
|
|
const colors = {
|
|
'connecting': 'bg-yellow-500',
|
|
'connected': 'bg-green-500',
|
|
'disconnected': 'bg-zinc-500',
|
|
'error': 'bg-red-500'
|
|
};
|
|
|
|
indicator.querySelectorAll('span').forEach(span => {
|
|
span.className = span.className.replace(/bg-\w+-\d+/g, colors[status] || colors.disconnected);
|
|
});
|
|
|
|
if (statusText) {
|
|
statusText.textContent = ` ${text}`;
|
|
}
|
|
}
|
|
}
|