Phase 3: Quick command enhancements with templates and history
Changes: - Added command templates modal with 12 common system commands - Added command history tracking (stored in localStorage) - History saves last 50 commands with timestamp and worker name - Template categories: system info, disk/memory, network, Docker, logs - Click templates to auto-fill command field - Click history items to reuse previous commands - Terminal-themed modals with green/amber styling - History persists across browser sessions Templates included: - System: uname, uptime, CPU info, processes - Resources: df -h, free -h, memory usage - Network: ip addr, active connections - Docker: container list - Logs: syslog tail, who is logged in, last logins Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -759,18 +759,23 @@
|
|||||||
<div id="quickcommand" class="tab-content">
|
<div id="quickcommand" class="tab-content">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>⚡ Quick Command Execution</h3>
|
<h3>⚡ Quick Command Execution</h3>
|
||||||
<p style="color: #666; margin-bottom: 20px;">Execute a command on selected workers instantly</p>
|
<p style="color: var(--terminal-green); margin-bottom: 20px;">Execute a command on selected workers instantly</p>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 10px; margin-bottom: 15px;">
|
||||||
|
<button onclick="showCommandTemplates()" style="flex: 0;">[ 📋 Templates ]</button>
|
||||||
|
<button onclick="showCommandHistory()" style="flex: 0;">[ 🕐 History ]</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label style="display: block; margin-bottom: 10px; font-weight: 600;">Select Worker:</label>
|
<label style="display: block; margin-bottom: 10px; font-weight: 600;">Select Worker:</label>
|
||||||
<select id="quickWorkerSelect">
|
<select id="quickWorkerSelect">
|
||||||
<option value="">Loading workers...</option>
|
<option value="">Loading workers...</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label style="display: block; margin-bottom: 10px; margin-top: 20px; font-weight: 600;">Command:</label>
|
<label style="display: block; margin-bottom: 10px; margin-top: 20px; font-weight: 600;">Command:</label>
|
||||||
<textarea id="quickCommand" placeholder="Enter command to execute (e.g., 'uptime' or 'df -h')"></textarea>
|
<textarea id="quickCommand" placeholder="Enter command to execute (e.g., 'uptime' or 'df -h')"></textarea>
|
||||||
|
|
||||||
<button onclick="executeQuickCommand()">▶️ Execute Command</button>
|
<button onclick="executeQuickCommand()">[ ▶️ Execute Command ]</button>
|
||||||
|
|
||||||
<div id="quickCommandResult" style="margin-top: 20px;"></div>
|
<div id="quickCommandResult" style="margin-top: 20px;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -803,7 +808,25 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h2>Execution Details</h2>
|
<h2>Execution Details</h2>
|
||||||
<div id="executionDetails"></div>
|
<div id="executionDetails"></div>
|
||||||
<button onclick="closeModal('viewExecutionModal')">Close</button>
|
<button onclick="closeModal('viewExecutionModal')">[ Close ]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Command Templates Modal -->
|
||||||
|
<div id="commandTemplatesModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h2>Command Templates</h2>
|
||||||
|
<div id="templateList" style="max-height: 400px; overflow-y: auto;"></div>
|
||||||
|
<button onclick="closeModal('commandTemplatesModal')">[ Close ]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Command History Modal -->
|
||||||
|
<div id="commandHistoryModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h2>Command History</h2>
|
||||||
|
<div id="historyList" style="max-height: 400px; overflow-y: auto;"></div>
|
||||||
|
<button onclick="closeModal('commandHistoryModal')">[ Close ]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1167,6 +1190,82 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Command Templates
|
||||||
|
const commandTemplates = [
|
||||||
|
{ name: 'System Info', cmd: 'uname -a', desc: 'Show system information' },
|
||||||
|
{ name: 'Uptime', cmd: 'uptime', desc: 'Show system uptime and load' },
|
||||||
|
{ name: 'Disk Usage', cmd: 'df -h', desc: 'Show disk space usage' },
|
||||||
|
{ name: 'Memory Usage', cmd: 'free -h', desc: 'Show memory usage' },
|
||||||
|
{ name: 'CPU Info', cmd: 'lscpu', desc: 'Show CPU information' },
|
||||||
|
{ name: 'Running Processes', cmd: 'ps aux --sort=-%mem | head -20', desc: 'Top 20 processes by memory' },
|
||||||
|
{ name: 'Network Interfaces', cmd: 'ip addr show', desc: 'Show network interfaces' },
|
||||||
|
{ name: 'Active Connections', cmd: 'ss -tunap', desc: 'Show active network connections' },
|
||||||
|
{ name: 'Docker Containers', cmd: 'docker ps -a', desc: 'List all Docker containers' },
|
||||||
|
{ name: 'System Log Tail', cmd: 'tail -n 50 /var/log/syslog', desc: 'Last 50 lines of system log' },
|
||||||
|
{ name: 'Who is Logged In', cmd: 'w', desc: 'Show logged in users' },
|
||||||
|
{ name: 'Last Logins', cmd: 'last -n 20', desc: 'Show last 20 logins' }
|
||||||
|
];
|
||||||
|
|
||||||
|
function showCommandTemplates() {
|
||||||
|
const html = commandTemplates.map((template, index) => `
|
||||||
|
<div class="template-item" onclick="useTemplate(${index})" style="cursor: pointer; padding: 12px; margin: 8px 0; background: #001a00; border: 1px solid #003300; border-left: 3px solid var(--terminal-green);">
|
||||||
|
<div style="color: var(--terminal-green); font-weight: bold; margin-bottom: 4px;">${template.name}</div>
|
||||||
|
<div style="color: var(--terminal-amber); font-family: var(--font-mono); font-size: 0.9em; margin-bottom: 4px;"><code>${escapeHtml(template.cmd)}</code></div>
|
||||||
|
<div style="color: #666; font-size: 0.85em;">${template.desc}</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
document.getElementById('templateList').innerHTML = html;
|
||||||
|
document.getElementById('commandTemplatesModal').classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function useTemplate(index) {
|
||||||
|
document.getElementById('quickCommand').value = commandTemplates[index].cmd;
|
||||||
|
closeModal('commandTemplatesModal');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCommandHistory() {
|
||||||
|
const history = JSON.parse(localStorage.getItem('commandHistory') || '[]');
|
||||||
|
|
||||||
|
if (history.length === 0) {
|
||||||
|
document.getElementById('historyList').innerHTML = '<div class="empty">No command history yet</div>';
|
||||||
|
} else {
|
||||||
|
const html = history.map((item, index) => `
|
||||||
|
<div class="history-item" onclick="useHistoryCommand(${index})" style="cursor: pointer; padding: 12px; margin: 8px 0; background: #001a00; border: 1px solid #003300; border-left: 3px solid var(--terminal-amber);">
|
||||||
|
<div style="color: var(--terminal-green); font-family: var(--font-mono); margin-bottom: 4px;"><code>${escapeHtml(item.command)}</code></div>
|
||||||
|
<div style="color: #666; font-size: 0.85em;">${new Date(item.timestamp).toLocaleString()} - ${item.worker}</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
document.getElementById('historyList').innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('commandHistoryModal').classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function useHistoryCommand(index) {
|
||||||
|
const history = JSON.parse(localStorage.getItem('commandHistory') || '[]');
|
||||||
|
document.getElementById('quickCommand').value = history[index].command;
|
||||||
|
closeModal('commandHistoryModal');
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToCommandHistory(command, workerName) {
|
||||||
|
const history = JSON.parse(localStorage.getItem('commandHistory') || '[]');
|
||||||
|
|
||||||
|
// Add to beginning, limit to 50 items
|
||||||
|
history.unshift({
|
||||||
|
command: command,
|
||||||
|
worker: workerName,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keep only last 50 commands
|
||||||
|
if (history.length > 50) {
|
||||||
|
history.splice(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('commandHistory', JSON.stringify(history));
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteWorker(workerId, name) {
|
async function deleteWorker(workerId, name) {
|
||||||
if (!confirm(`Delete worker: ${name}?`)) return;
|
if (!confirm(`Delete worker: ${name}?`)) return;
|
||||||
|
|
||||||
@@ -1248,31 +1347,39 @@
|
|||||||
async function executeQuickCommand() {
|
async function executeQuickCommand() {
|
||||||
const workerId = document.getElementById('quickWorkerSelect').value;
|
const workerId = document.getElementById('quickWorkerSelect').value;
|
||||||
const command = document.getElementById('quickCommand').value;
|
const command = document.getElementById('quickCommand').value;
|
||||||
|
|
||||||
if (!workerId || !command) {
|
if (!workerId || !command) {
|
||||||
alert('Please select a worker and enter a command');
|
alert('Please select a worker and enter a command');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find worker name for history
|
||||||
|
const worker = workers.find(w => w.id === workerId);
|
||||||
|
const workerName = worker ? worker.name : 'Unknown';
|
||||||
|
|
||||||
const resultDiv = document.getElementById('quickCommandResult');
|
const resultDiv = document.getElementById('quickCommandResult');
|
||||||
resultDiv.innerHTML = '<div class="loading">Executing command...</div>';
|
resultDiv.innerHTML = '<div class="loading">Executing command...</div>';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/workers/${workerId}/command`, {
|
const response = await fetch(`/api/workers/${workerId}/command`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ command })
|
body: JSON.stringify({ command })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Add to command history
|
||||||
|
addToCommandHistory(command, workerName);
|
||||||
|
|
||||||
resultDiv.innerHTML = `
|
resultDiv.innerHTML = `
|
||||||
<div style="background: #f0fdf4; border: 2px solid #86efac; padding: 15px; border-radius: 5px;">
|
<div style="background: #001a00; border: 2px solid var(--terminal-green); padding: 15px;">
|
||||||
<strong style="color: #166534;">✓ Command sent successfully!</strong>
|
<strong style="color: var(--terminal-green);">✓ Command sent successfully!</strong>
|
||||||
<div style="margin-top: 10px; font-family: monospace; font-size: 0.9em;">
|
<div style="margin-top: 10px; font-family: var(--font-mono); font-size: 0.9em; color: var(--terminal-green);">
|
||||||
Execution ID: ${data.execution_id}
|
Execution ID: ${data.execution_id}
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top: 10px; color: #166534;">
|
<div style="margin-top: 10px; color: var(--terminal-amber);">
|
||||||
Check the Executions tab to see the results
|
Check the Executions tab to see the results
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user