librelad c22b0ac60d chore(webui): strip ~665 commented-out console.* debug lines
The shipped frontend carried ~600 muted '// console.…' debug statements (and
their multi-line commented continuation lines) left over from development —
clutter across 30 files. Removed them with a guarded pass that ONLY ever deletes
lines starting with // (so it can never alter behaviour), consuming each
commented console opener plus its continuation comment lines until the
string-stripped parens balance.

665 lines removed, 30 files; 0 insertions. Verified every deleted line is a //
comment (no code touched), real prose comments preserved, full node --check
sweep clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 01:25:41 +01:00

352 lines
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Auto-extracted from tasks-manager.js (verbatim) — augments TasksManager.prototype. Loaded after the base.
Object.assign(TasksManager.prototype, {
// SSE-driven log streaming. Subscribes to `taskLog` events from the bus and
// appends incoming chunks. Initial backlog is fetched once via the API.
//
// Resilient to DOM replacement: the logs container can be wiped out by
// `renderTasks()` (the 30s auto-refresh) or by `loadTaskLogs()` (toggle
// re-open). Instead of capturing a `preElement` reference once, `render()`
// re-locates / re-creates it on every call from the cumulative `buffered`
// string. Idempotent: if already streaming, a second call just re-renders.
async startLogStreaming(taskId, task) {
if (!this.activeLogStreams) this.activeLogStreams = new Map();
if (this.activeLogStreams.has(taskId)) {
const existing = this.activeLogStreams.get(taskId);
if (existing && typeof existing.render === 'function') existing.render();
return;
}
const state = { buffered: '' };
const render = () => {
const logsContainer = document.getElementById(`logs-${taskId}`);
if (!logsContainer) return;
const overlay = logsContainer.querySelector('div[style*="position: absolute"]');
if (overlay) overlay.remove();
let preElement = logsContainer.querySelector('pre.output-content');
if (!preElement) {
logsContainer.innerHTML = '';
preElement = document.createElement('pre');
preElement.className = 'output-content terminal-style';
logsContainer.appendChild(preElement);
}
const atBottom = logsContainer.scrollHeight - logsContainer.scrollTop <= logsContainer.clientHeight + 10;
if (state.buffered) {
preElement.innerHTML = this.taskManager.parseAnsiColors(state.buffered);
} else {
preElement.innerHTML = '<span style="color: #888;">Waiting for logs...</span>';
}
if (atBottom) logsContainer.scrollTop = logsContainer.scrollHeight;
};
// Initial backlog via the API.
try {
const initial = await this.taskManager.readFullTaskLog(taskId);
if (initial && initial.length) state.buffered = initial;
} catch { /* fall through to placeholder */ }
render();
const onLog = (event) => {
const detail = event.detail || {};
if (detail.id !== taskId || typeof detail.chunk !== 'string') return;
state.buffered += detail.chunk;
render();
};
window.addEventListener('taskLog', onLog);
// SSE catch-up: when the backend restarts mid-task (e.g., libreportal
// recreates itself during a CrowdSec install), the SSE event source
// drops. EventSource auto-reconnects, but task.log events emitted
// during the gap are lost. taskBusReady fires after every reconnect —
// pull the missed bytes via the existing /:id/log?position=N endpoint
// and splice them in. Skips on the initial connect (no gap).
let initialReadyFired = false;
const onBusReady = async () => {
if (!initialReadyFired) { initialReadyFired = true; return; }
try {
const res = await fetch(`/api/tasks/${encodeURIComponent(taskId)}/log?position=${state.buffered.length}`);
if (!res.ok) return;
const missed = await res.text();
if (missed) {
state.buffered += missed;
render();
}
} catch { /* network blip, next ready will retry */ }
};
window.addEventListener('taskBusReady', onBusReady);
this.activeLogStreams.set(taskId, {
stream: { stop: () => {
window.removeEventListener('taskLog', onLog);
window.removeEventListener('taskBusReady', onBusReady);
this.activeLogStreams.delete(taskId);
} },
render,
isPaused: false
});
},
// Stop log streaming for a task
stopLogStreaming(taskId) {
if (this.activeLogStreams && this.activeLogStreams.has(taskId)) {
const streamData = this.activeLogStreams.get(taskId);
streamData.stream.stop();
this.activeLogStreams.delete(taskId);
}
},
// Load task logs automatically
async loadTaskLogs(taskId) {
try {
const logsContainer = document.getElementById(`logs-${taskId}`);
if (!logsContainer) {
console.warn(`⚠️ Logs container not found for task ${taskId}`);
return;
}
const task = this.tasks.find(t => t.id === taskId);
const inMemoryLog = (task && Array.isArray(task.log) && task.log.length > 0) ? task.log : null;
const renderInMemory = () => {
if (!inMemoryLog) return false;
logsContainer.innerHTML = inMemoryLog
.map(line => `<div class="log-entry">${this.taskManager.parseAnsiColors(line)}</div>`)
.join('');
return true;
};
logsContainer.innerHTML = '<div class="log-entry">🔄 Loading logs...</div>';
const isScrolledToBottom = logsContainer.scrollHeight - logsContainer.scrollTop <= logsContainer.clientHeight + 10;
const logResponse = await fetch(`/read-file?path=tasks/${taskId}.log`);
if (logResponse.ok) {
const logContent = await logResponse.text();
if (logContent.trim()) {
logsContainer.innerHTML = `<pre class="output-content terminal-style">${this.taskManager.parseAnsiColors(logContent)}</pre>`;
if (isScrolledToBottom) logsContainer.scrollTop = logsContainer.scrollHeight;
return;
}
}
if (renderInMemory()) return;
logsContainer.innerHTML = '<div class="log-entry"> No logs available for this task.</div>';
} catch (error) {
console.error(`❌ Error loading logs for task ${taskId}:`, error);
const logsContainer = document.getElementById(`logs-${taskId}`);
if (logsContainer) {
logsContainer.innerHTML = '<div class="log-entry">❌ Failed to load logs.</div>';
}
}
},
// Load task output on demand
async loadTaskOutput(taskId) {
try {
// Read the task file to get output
const task = await this.taskManager.getTask(taskId);
if (!task) return;
const outputElement = document.querySelector(`[data-task-id="${taskId}"] .task-output`);
if (!outputElement) return;
// Show loading state
outputElement.innerHTML = `
<div class="loading-output">Loading output...</div>
`;
// Check if task has output
if (task.output && task.output.trim()) {
outputElement.innerHTML = `
<h4>📤 Output</h4>
<pre class="output-content terminal-style">${this.taskManager.parseAnsiColors(task.output)}</pre>
`;
} else if (task.error && task.error.trim()) {
outputElement.innerHTML = `
<h4>❌ Error</h4>
<pre class="error-content">${this.escapeHtml(task.error)}</pre>
`;
} else {
// Try to read from log file
const logResponse = await fetch(`/read-file?path=tasks/${taskId}.log`);
if (logResponse.ok) {
const logContent = await logResponse.text();
if (logContent.trim()) {
outputElement.innerHTML = `
<pre class="output-content terminal-style">${this.taskManager.parseAnsiColors(logContent)}</pre>
`;
} else {
outputElement.innerHTML = `
<h4> Information</h4>
<div class="info-content">No output available for this task.</div>
`;
}
} else {
outputElement.innerHTML = `
<h4> Information</h4>
<div class="info-content">No output available for this task.</div>
`;
}
}
} catch (error) {
console.error(`❌ Error loading task output for ${taskId}:`, error);
const outputElement = document.querySelector(`[data-task-id="${taskId}"] .task-output`);
if (outputElement) {
outputElement.innerHTML = `
<h4>❌ Error</h4>
<div class="error-content">Failed to load task output: ${error.message}</div>
`;
}
}
},
// Start global live log updater - simple 2-second updates for all running tasks
startGlobalLiveLogUpdater() {
// Update every 2 seconds
setInterval(async () => {
// Find all running tasks
const runningTasks = this.tasks.filter(task => task.status === 'running');
if (runningTasks.length > 0) {
// Update each running task's live logs
for (const task of runningTasks) {
await this.updateLiveLogsSimple(task.id);
}
} else {
}
}, 2000); // Every 2 seconds
},
// Simple live log update - no complex polling logic
async updateLiveLogsSimple(taskId) {
const liveLogsElement = document.getElementById(`live-logs-${taskId}`);
if (!liveLogsElement) {
return; // Silently skip if element not found
}
try {
// Read the log file content
const response = await fetch(`/read-file?path=tasks/${taskId}.log`);
if (response.ok) {
const logContent = await response.text();
if (logContent.trim()) {
// Split into lines and display
const lines = logContent.split('\n').filter(line => line.trim());
liveLogsElement.innerHTML = lines.map(line =>
`<div class="log-entry">${this.parseAnsiColors(line)}</div>`
).join('');
// Auto-scroll to bottom
liveLogsElement.scrollTop = liveLogsElement.scrollHeight;
} else {
liveLogsElement.innerHTML = '<div class="log-entry">🔄 Waiting for logs...</div>';
}
} else {
console.warn(`⚠️ Failed to read log file for task ${taskId}: ${response.status}`);
liveLogsElement.innerHTML = '<div class="log-entry">⚠️ Unable to read logs</div>';
}
} catch (error) {
// Silently handle errors
console.warn(`⚠️ Error reading live logs for task ${taskId}:`, error);
liveLogsElement.innerHTML = '<div class="log-entry">❌ Error loading logs</div>';
}
},
// Update task structure for live logs
updateTaskStructure(taskId, task) {
const taskElement = document.querySelector(`[data-task-id="${taskId}"]`);
if (!taskElement) return;
const detailsElement = taskElement.querySelector('.task-details');
if (!detailsElement) return;
// Check if logs container already exists
const existingLogs = detailsElement.querySelector('.task-logs .log-container');
if (existingLogs) {
return; // Already exists, no need to update
}
// Add simplified logs section for running tasks
if (task.status === 'running') {
const logsHtml = `
<div class="task-logs">
<div class="log-container terminal-style" id="logs-${taskId}" style="height: 200px; overflow-y: auto; position: relative;">
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(10, 18, 36, 0.85); display: flex; align-items: center; justify-content: center; z-index: 10;"><div class="loading-spinner" style="width: 16px; height: 16px; border: 2px solid rgba(255,255,255,0.18); border-top: 2px solid #fff; border-radius: 50%; animation: spin 1s linear infinite; margin-right: 8px;"></div>Loading logs...</div></div>
</div>
</div>
`;
// Insert logs section at the bottom of details
detailsElement.insertAdjacentHTML('beforeend', logsHtml);
// Auto-load logs
this.loadTaskLogs(taskId);
}
},
// Update task display in real-time
updateTaskDisplay(task) {
const taskElement = document.querySelector(`[data-task-id="${task.id}"]`);
if (!taskElement) {
// The monitored task isn't always rendered (different tab, list filtered out,
// task already removed). Silently skip — this is the normal case.
return;
}
// Update status and content
const statusElement = taskElement.querySelector('.task-status');
const contentElement = taskElement.querySelector('.task-content');
if (statusElement) {
const statusClass = `status-${task.status || 'unknown'}`;
statusElement.className = `task-status ${statusClass}`;
statusElement.innerHTML = `${this.getStatusIcon(task.status)} ${task.status ? task.status.toUpperCase() : 'UNKNOWN'}`;
} else {
console.warn(`⚠️ Status element not found for task ${task.id}`);
}
// Mirror the status into the details panel's metadata block too — that
// copy of the status was previously left stale until the page reloaded.
const detailsStatus = taskElement.querySelector(`#details-${task.id} .task-meta .status-running, #details-${task.id} .task-meta .status-queued, #details-${task.id} .task-meta .status-pending, #details-${task.id} .task-meta .status-completed, #details-${task.id} .task-meta .status-failed, #details-${task.id} .task-meta .status-cancelled, #details-${task.id} .task-meta [class^="status-"]`);
if (detailsStatus) {
detailsStatus.className = `status-${task.status || 'unknown'}`;
detailsStatus.innerHTML = `${this.getStatusIcon(task.status)} ${task.status ? task.status.toUpperCase() : 'UNKNOWN'}`;
}
if (contentElement) {
contentElement.textContent = task.command;
}
},
// Update highlighted task status and UI
async updateHighlightedTaskStatus(taskId) {
try {
// Use lightweight summary for status updates
const task = await this.taskManager.getTaskSummary(taskId);
if (!task) return;
// Update task display
this.updateTaskDisplay(task);
// If task completed or failed, always load output
if ((task.status === 'completed' || task.status === 'failed')) {
const details = document.getElementById(`details-${taskId}`);
if (details && details.style.display === 'block') {
// Load output regardless of current content
this.loadTaskOutput(taskId);
} else if (details) {
// If details aren't open, mark for loading when opened
details.setAttribute('data-load-output-on-open', 'true');
}
}
} catch (error) {
console.error(`❌ Error updating highlighted task status for ${taskId}:`, error);
}
} ,
});