204 lines
6.0 KiB
JavaScript
204 lines
6.0 KiB
JavaScript
import { TIMEOUTS } from '../constants.js';
|
|
|
|
function formatFileSize(bytes) {
|
|
if (bytes < 1024) return bytes + ' B';
|
|
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
}
|
|
|
|
export function createLogModal() {
|
|
let _modalEl = null;
|
|
let _keydownHandler = null;
|
|
let _refreshTimer = null;
|
|
let _fetching = false;
|
|
let _fetchLog = null;
|
|
let _onClose = null;
|
|
let _logContentEl = null;
|
|
let _statusBarEl = null;
|
|
let _refreshCheckbox = null;
|
|
|
|
const updateLogView = (logData) => {
|
|
if (!_logContentEl || !_statusBarEl || !logData) {
|
|
return;
|
|
}
|
|
|
|
const shouldAutoScroll =
|
|
_logContentEl.scrollTop + _logContentEl.clientHeight >= _logContentEl.scrollHeight - 50;
|
|
_logContentEl.textContent = logData.content || '';
|
|
if (shouldAutoScroll) {
|
|
_logContentEl.scrollTop = _logContentEl.scrollHeight;
|
|
}
|
|
|
|
let statusText;
|
|
if (logData.source === "memory") {
|
|
statusText = "Remote worker log (in-memory buffer)";
|
|
if (logData.truncated) {
|
|
statusText += ` (showing last ${logData.lines_shown || 0} lines)`;
|
|
}
|
|
} else {
|
|
statusText = `Log file: ${logData.log_file || 'unknown'}`;
|
|
if (logData.truncated) {
|
|
statusText += ` (showing last ${logData.lines_shown} lines of ${formatFileSize(logData.file_size || 0)})`;
|
|
}
|
|
}
|
|
_statusBarEl.textContent = statusText;
|
|
};
|
|
|
|
const refreshLog = async () => {
|
|
if (_fetching || !_fetchLog || !_refreshCheckbox?.checked) {
|
|
return;
|
|
}
|
|
_fetching = true;
|
|
try {
|
|
const data = await _fetchLog();
|
|
if (data) {
|
|
updateLogView(data);
|
|
}
|
|
} catch (_error) {
|
|
// Keep the modal open and continue retrying on next interval.
|
|
} finally {
|
|
_fetching = false;
|
|
}
|
|
};
|
|
|
|
const stopRefresh = () => {
|
|
if (_refreshTimer) {
|
|
clearInterval(_refreshTimer);
|
|
_refreshTimer = null;
|
|
}
|
|
};
|
|
|
|
const startRefresh = () => {
|
|
stopRefresh();
|
|
_refreshTimer = setInterval(() => {
|
|
refreshLog();
|
|
}, TIMEOUTS.LOG_REFRESH);
|
|
};
|
|
|
|
const unmount = () => {
|
|
stopRefresh();
|
|
|
|
if (_keydownHandler) {
|
|
document.removeEventListener('keydown', _keydownHandler);
|
|
_keydownHandler = null;
|
|
}
|
|
|
|
if (_modalEl) {
|
|
_modalEl.remove();
|
|
_modalEl = null;
|
|
}
|
|
|
|
const onClose = _onClose;
|
|
_onClose = null;
|
|
if (onClose) {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
const mount = (container, { workerName, logData, onClose, fetchLog, themeClass = "" }) => {
|
|
_onClose = onClose;
|
|
_fetchLog = fetchLog;
|
|
|
|
const modal = document.createElement('div');
|
|
modal.id = 'distributed-log-modal';
|
|
modal.className = 'log-modal';
|
|
if (themeClass) {
|
|
modal.classList.add(themeClass);
|
|
}
|
|
|
|
const content = document.createElement('div');
|
|
content.className = 'log-modal__content';
|
|
|
|
const header = document.createElement('div');
|
|
header.className = 'log-modal__header';
|
|
|
|
const title = document.createElement('h3');
|
|
title.className = 'log-modal__title';
|
|
title.textContent = `${workerName} - Log Viewer`;
|
|
|
|
const headerButtons = document.createElement('div');
|
|
headerButtons.className = 'log-modal__header-buttons';
|
|
|
|
const refreshContainer = document.createElement('div');
|
|
refreshContainer.className = 'log-modal__refresh';
|
|
|
|
const refreshCheckbox = document.createElement('input');
|
|
refreshCheckbox.type = 'checkbox';
|
|
refreshCheckbox.id = 'log-auto-refresh';
|
|
refreshCheckbox.className = 'log-modal__refresh-input';
|
|
refreshCheckbox.checked = true;
|
|
|
|
const refreshLabel = document.createElement('label');
|
|
refreshLabel.htmlFor = 'log-auto-refresh';
|
|
refreshLabel.className = 'log-modal__refresh-label';
|
|
refreshLabel.textContent = 'Auto-refresh';
|
|
|
|
refreshContainer.appendChild(refreshCheckbox);
|
|
refreshContainer.appendChild(refreshLabel);
|
|
|
|
const closeBtn = document.createElement('button');
|
|
closeBtn.className = 'distributed-button log-modal__close';
|
|
closeBtn.textContent = '✕';
|
|
|
|
headerButtons.appendChild(refreshContainer);
|
|
headerButtons.appendChild(closeBtn);
|
|
|
|
header.appendChild(title);
|
|
header.appendChild(headerButtons);
|
|
|
|
const logContainer = document.createElement('div');
|
|
logContainer.className = 'log-modal__body';
|
|
logContainer.id = 'distributed-log-content';
|
|
|
|
const statusBar = document.createElement('div');
|
|
statusBar.className = 'log-modal__status';
|
|
|
|
content.appendChild(header);
|
|
content.appendChild(logContainer);
|
|
content.appendChild(statusBar);
|
|
modal.appendChild(content);
|
|
|
|
closeBtn.addEventListener('click', unmount);
|
|
modal.addEventListener('click', (event) => {
|
|
if (event.target === modal) {
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
_keydownHandler = (event) => {
|
|
if (event.key === 'Escape') {
|
|
unmount();
|
|
}
|
|
};
|
|
document.addEventListener('keydown', _keydownHandler);
|
|
|
|
_modalEl = modal;
|
|
_logContentEl = logContainer;
|
|
_statusBarEl = statusBar;
|
|
_refreshCheckbox = refreshCheckbox;
|
|
|
|
refreshCheckbox.addEventListener('change', () => {
|
|
if (refreshCheckbox.checked) {
|
|
refreshLog();
|
|
}
|
|
});
|
|
|
|
container.appendChild(modal);
|
|
updateLogView(logData);
|
|
|
|
requestAnimationFrame(() => {
|
|
if (_logContentEl) {
|
|
_logContentEl.scrollTop = _logContentEl.scrollHeight;
|
|
}
|
|
});
|
|
|
|
startRefresh();
|
|
};
|
|
|
|
return {
|
|
mount,
|
|
unmount,
|
|
update: updateLogView,
|
|
};
|
|
}
|