11
index.js
11
index.js
@@ -72,6 +72,7 @@ app.get('/tail/api/files', (req, res) => {
|
|||||||
app.get('/tail/api/files/:endpoint', (req, res) => {
|
app.get('/tail/api/files/:endpoint', (req, res) => {
|
||||||
const endpoint = path.normalize(req.params.endpoint)
|
const endpoint = path.normalize(req.params.endpoint)
|
||||||
const file = path.join(directory, endpoint)
|
const file = path.join(directory, endpoint)
|
||||||
|
const isReconnect = req.query.reconnect === 'true'
|
||||||
|
|
||||||
// Security check - ensure file is within the directory
|
// Security check - ensure file is within the directory
|
||||||
if (!file.startsWith(path.resolve(directory))) {
|
if (!file.startsWith(path.resolve(directory))) {
|
||||||
@@ -95,14 +96,20 @@ app.get('/tail/api/files/:endpoint', (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// For reconnections, start from current end, for new connections start from 'end'
|
||||||
|
const beginAt = isReconnect ? fs.statSync(file).size : 'end'
|
||||||
|
|
||||||
const stream = ts.createReadStream(file, {
|
const stream = ts.createReadStream(file, {
|
||||||
beginAt: 'end',
|
beginAt: beginAt,
|
||||||
endOnError: true,
|
endOnError: true,
|
||||||
detectTruncate: true
|
detectTruncate: true
|
||||||
})
|
})
|
||||||
|
|
||||||
// Send initial message
|
// Send initial message
|
||||||
res.write(Buffer.from(`data: Tailing ${endpoint}...\n\n`))
|
const initialMessage = isReconnect
|
||||||
|
? `Reconnected to ${endpoint}...`
|
||||||
|
: `Tailing ${endpoint}...`
|
||||||
|
res.write(Buffer.from(`data: ${initialMessage}\n\n`))
|
||||||
|
|
||||||
// Handle stream events
|
// Handle stream events
|
||||||
stream.on('error', error => {
|
stream.on('error', error => {
|
||||||
|
|||||||
@@ -571,6 +571,32 @@ input:checked + .slider:before {
|
|||||||
background: rgba(6, 182, 212, 0.1);
|
background: rgba(6, 182, 212, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-line.reconnect-message {
|
||||||
|
border-left-color: var(--warning-color);
|
||||||
|
background: rgba(245, 158, 11, 0.15);
|
||||||
|
color: var(--warning-color);
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
margin: var(--spacing-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line.reconnect-message i {
|
||||||
|
margin-right: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line.reconnect-success {
|
||||||
|
border-left-color: var(--success-color);
|
||||||
|
background: rgba(16, 185, 129, 0.15);
|
||||||
|
color: var(--success-color);
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
margin: var(--spacing-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line.reconnect-success i {
|
||||||
|
margin-right: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
/* Loading states */
|
/* Loading states */
|
||||||
.loading {
|
.loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ class LogTailMonitor {
|
|||||||
/**
|
/**
|
||||||
* Start tailing a specific file
|
* Start tailing a specific file
|
||||||
*/
|
*/
|
||||||
async startTailing(filename) {
|
async startTailing(filename, isReconnect = false) {
|
||||||
this.showLoading('Connecting to ' + filename + '...');
|
this.showLoading('Connecting to ' + filename + '...');
|
||||||
|
|
||||||
// Stop current connection
|
// Stop current connection
|
||||||
@@ -254,17 +254,28 @@ class LogTailMonitor {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear existing logs
|
// Clear existing logs only if it's not a reconnection
|
||||||
this.clearLogs();
|
if (!isReconnect) {
|
||||||
|
this.clearLogs();
|
||||||
|
} else {
|
||||||
|
// Add a reconnection indicator to the existing logs
|
||||||
|
this.addReconnectionMessage();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Start SSE connection
|
// Start SSE connection with reconnect parameter if needed
|
||||||
this.eventSource = new EventSource(`api/files/${encodeURIComponent(filename)}`);
|
const url = `api/files/${encodeURIComponent(filename)}${isReconnect ? '?reconnect=true' : ''}`;
|
||||||
|
this.eventSource = new EventSource(url);
|
||||||
|
|
||||||
this.eventSource.onopen = () => {
|
this.eventSource.onopen = () => {
|
||||||
this.hideLoading();
|
this.hideLoading();
|
||||||
this.setConnectionStatus(true);
|
this.setConnectionStatus(true);
|
||||||
console.log('SSE connection established');
|
console.log('SSE connection established');
|
||||||
|
|
||||||
|
// If this is a reconnection, add a success message
|
||||||
|
if (isReconnect) {
|
||||||
|
this.addReconnectionSuccessMessage();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.eventSource.onmessage = (event) => {
|
this.eventSource.onmessage = (event) => {
|
||||||
@@ -283,11 +294,11 @@ class LogTailMonitor {
|
|||||||
console.log('SSE connection closed');
|
console.log('SSE connection closed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-reconnect after delay
|
// Auto-reconnect after delay, but don't clear logs
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.currentFile && this.eventSource.readyState === EventSource.CLOSED) {
|
if (this.currentFile && this.eventSource.readyState === EventSource.CLOSED) {
|
||||||
console.log('Attempting to reconnect...');
|
console.log('Attempting to reconnect...');
|
||||||
this.startTailing(this.currentFile);
|
this.startTailing(this.currentFile, true); // true = isReconnect
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
};
|
};
|
||||||
@@ -568,6 +579,36 @@ class LogTailMonitor {
|
|||||||
this.elements.loadingOverlay.classList.remove('visible');
|
this.elements.loadingOverlay.classList.remove('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a reconnection message to the log
|
||||||
|
*/
|
||||||
|
addReconnectionMessage() {
|
||||||
|
const reconnectLine = document.createElement('div');
|
||||||
|
reconnectLine.className = 'log-line reconnect-message';
|
||||||
|
reconnectLine.innerHTML = '<i class="fas fa-plug"></i> Reconnecting to log stream...';
|
||||||
|
|
||||||
|
this.elements.logOutput.appendChild(reconnectLine);
|
||||||
|
|
||||||
|
if (this.autoScroll) {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a successful reconnection message to the log
|
||||||
|
*/
|
||||||
|
addReconnectionSuccessMessage() {
|
||||||
|
const successLine = document.createElement('div');
|
||||||
|
successLine.className = 'log-line reconnect-success';
|
||||||
|
successLine.innerHTML = '<i class="fas fa-check-circle"></i> Successfully reconnected - continuing log stream...';
|
||||||
|
|
||||||
|
this.elements.logOutput.appendChild(successLine);
|
||||||
|
|
||||||
|
if (this.autoScroll) {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escape HTML to prevent XSS
|
* Escape HTML to prevent XSS
|
||||||
*/
|
*/
|
||||||
@@ -580,15 +621,18 @@ class LogTailMonitor {
|
|||||||
|
|
||||||
// Initialize the application when DOM is ready
|
// Initialize the application when DOM is ready
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
new LogTailMonitor();
|
window.logMonitor = new LogTailMonitor();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle page visibility changes
|
// Handle page visibility changes - attempt reconnection instead of reload
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
if (document.visibilityState === 'visible') {
|
if (document.visibilityState === 'visible' && window.logMonitor) {
|
||||||
// Refresh connection when page becomes visible
|
// Try to reconnect to current file if connection was lost
|
||||||
setTimeout(() => {
|
if (window.logMonitor.currentFile && (!window.logMonitor.eventSource || window.logMonitor.eventSource.readyState === EventSource.CLOSED)) {
|
||||||
window.location.reload();
|
console.log('Page became visible, attempting to reconnect...');
|
||||||
}, 1000);
|
setTimeout(() => {
|
||||||
|
window.logMonitor.startTailing(window.logMonitor.currentFile, true);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user