const { ipcRenderer } = require('electron'); let currentAgent = null; let allTasks = []; let filteredTasks = []; let currentFilter = 'all'; let isEditing = false; let expandedTaskId = null; // Track which task is expanded // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', () => { setupEventListeners(); // Auto-refresh every 2 seconds, but only if not editing setInterval(() => { if (!isEditing) { refreshTasks(); } }, 2000); }); // Listen for agent data from main process ipcRenderer.on('agent-data', (event, agent) => { currentAgent = agent; updateAgentInfo(); refreshTasks(); }); // Listen for real-time task updates ipcRenderer.on('task-removed', (event, taskId) => { removeTaskFromList(taskId); refreshDisplay(); }); ipcRenderer.on('task-modified', (event, task) => { updateTaskInList(task); refreshDisplay(); }); ipcRenderer.on('tasks-reordered', (event, tasks) => { allTasks = tasks || []; refreshDisplay(); }); function setupEventListeners() { // Filter change document.getElementById('statusFilter').addEventListener('change', filterTasks); // Keyboard shortcuts document.addEventListener('keydown', (event) => { if (event.key === 'F5' || (event.ctrlKey && event.key === 'r')) { event.preventDefault(); refreshTasks(); } if (event.key === 'Escape') { // Cancel any active editing cancelAllEditing(); } }); } function updateAgentInfo() { if (!currentAgent) return; document.getElementById('agentId').textContent = currentAgent.agentid || 'Unknown'; const details = [ currentAgent.hostname || 'Unknown Host', currentAgent.username || 'Unknown User', Array.isArray(currentAgent.IP) ? currentAgent.IP.join(', ') : (currentAgent.IP || 'Unknown IP') ].join(' • '); document.getElementById('agentDetails').textContent = details; } async function refreshTasks() { if (!currentAgent) return; try { const tasks = await ipcRenderer.invoke('get-agent-tasks', currentAgent.agentid); allTasks = tasks || []; refreshDisplay(); } catch (error) { console.error('Error fetching tasks:', error); } } function refreshDisplay() { updateStats(); filterTasks(); } function updateStats() { const stats = { starting: 0, queued: 0, processing: 0, completed: 0, error: 0 }; allTasks.forEach(task => { if (stats.hasOwnProperty(task.status)) { stats[task.status]++; } }); document.getElementById('queuedCount').textContent = stats.queued; document.getElementById('processingCount').textContent = stats.processing; document.getElementById('completedCount').textContent = stats.completed; document.getElementById('errorCount').textContent = stats.error; } function filterTasks() { const filter = document.getElementById('statusFilter').value; currentFilter = filter; if (filter === 'all') { filteredTasks = [...allTasks]; } else { filteredTasks = allTasks.filter(task => task.status === filter); } renderTasks(); } function renderTasks() { const taskList = document.getElementById('taskList'); if (filteredTasks.length === 0) { taskList.innerHTML = '
No tasks found
'; return; } taskList.innerHTML = ''; filteredTasks.forEach((task, index) => { const taskElement = createTaskElement(task, index); taskList.appendChild(taskElement); // Add click handler for completed or error tasks to expand/collapse if (task.status === 'completed' || task.status === 'error') { taskElement.addEventListener('click', (e) => { // Only expand/collapse if not clicking a button if (e.target.classList.contains('action-btn')) return; if (expandedTaskId === task.taskid) { expandedTaskId = null; } else { expandedTaskId = task.taskid; } renderTasks(); }); } }); } function createTaskElement(task, index) { const taskItem = document.createElement('div'); taskItem.className = `task-item ${task.status === 'processing' ? 'processing' : ''}`; taskItem.dataset.taskId = task.taskid; const queuePosition = currentFilter === 'queued' ? index + 1 : null; let outputSection = ''; if ((task.status === 'completed' || task.status === 'error') && expandedTaskId === task.taskid) { outputSection = `
${escapeHtml(task.output || (task.status === 'error' ? 'No error output' : 'No output'))}
`; } taskItem.innerHTML = ` ${queuePosition ? `
${queuePosition}
` : ''}
${escapeHtml(task.command)}
${task.status} ID: ${task.taskid} Output: ${task.outputChannel || 'N/A'} Upload: ${task.uploadChannel || 'N/A'}
${outputSection}
${createActionButtons(task)}
`; return taskItem; } function createActionButtons(task) { const isProcessing = task.status === 'processing'; const isCompleted = task.status === 'completed'; const isError = task.status === 'error'; const isQueued = task.status === 'queued'; const canModify = !isProcessing && !isCompleted && !isError; let buttons = ''; // No edit or remove for error tasks if (isError) { return buttons; } if (canModify) { buttons += ``; } // For queued tasks, show a red X for removal (no prompt) if (isQueued) { buttons += ``; } else if (canModify) { // For other modifiable tasks (shouldn't happen, but fallback) buttons += ``; } if (isProcessing) { buttons += ``; } return buttons; } async function editTask(taskId) { const task = allTasks.find(t => t.taskid === taskId); if (!task) return; isEditing = true; const commandElement = document.getElementById(`command-${taskId}`); const originalCommand = task.command; // Create input field const input = document.createElement('input'); input.type = 'text'; input.className = 'edit-input'; input.value = originalCommand; // Replace command text with input commandElement.innerHTML = ''; commandElement.appendChild(input); input.focus(); input.select(); // Handle save/cancel const saveEdit = async () => { const newCommand = input.value.trim(); if (newCommand && newCommand !== originalCommand) { try { const result = await ipcRenderer.invoke('modify-task', currentAgent.agentid, taskId, newCommand); if (result.success) { task.command = newCommand; commandElement.textContent = newCommand; } else { alert(`Error: ${result.message}`); commandElement.textContent = originalCommand; } } catch (error) { console.error('Error modifying task:', error); alert('Error modifying task'); commandElement.textContent = originalCommand; } } else { commandElement.textContent = originalCommand; } isEditing = false; }; const cancelEdit = () => { commandElement.textContent = originalCommand; isEditing = false; }; input.addEventListener('keydown', (event) => { if (event.key === 'Enter') { event.preventDefault(); saveEdit(); } else if (event.key === 'Escape') { event.preventDefault(); cancelEdit(); } }); input.addEventListener('blur', saveEdit); } // Remove task immediately, no prompt async function removeTaskNoPrompt(taskId) { try { const result = await ipcRenderer.invoke('remove-task', currentAgent.agentid, taskId); if (result.success) { removeTaskFromList(taskId); refreshDisplay(); } else { alert(`Error: ${result.message}`); } } catch (error) { console.error('Error removing task:', error); alert('Error removing task'); } } async function moveTask(taskId, direction) { try { const result = await ipcRenderer.invoke('reorder-task', currentAgent.agentid, taskId, direction); if (!result.success) { alert(`Error: ${result.message}`); } // The tasks-reordered event will handle the UI update } catch (error) { console.error('Error moving task:', error); alert('Error moving task'); } } // Remove completed and error tasks async function clearCompleted() { const completedOrErrorTasks = allTasks.filter(task => task.status === 'completed' || task.status === 'error'); if (completedOrErrorTasks.length === 0) { alert('No completed or error tasks to clear'); return; } if (!confirm(`Are you sure you want to remove ${completedOrErrorTasks.length} completed/error task(s)?`)) { return; } try { for (const task of completedOrErrorTasks) { await ipcRenderer.invoke('remove-task', currentAgent.agentid, task.taskid); } refreshTasks(); } catch (error) { console.error('Error clearing completed/error tasks:', error); alert('Error clearing completed/error tasks'); } } function removeTaskFromList(taskId) { allTasks = allTasks.filter(task => task.taskid !== taskId); } function updateTaskInList(updatedTask) { const index = allTasks.findIndex(task => task.taskid === updatedTask.taskid); if (index !== -1) { allTasks[index] = updatedTask; } } function cancelAllEditing() { if (isEditing) { const activeInput = document.querySelector('.edit-input'); if (activeInput) { activeInput.dispatchEvent(new Event('blur')); } } } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }