Phase 7: Multi-Worker Command Execution

Added ability to execute commands on multiple workers simultaneously:

- Added execution mode selector (Single/Multiple Workers)
- Multi-worker mode with checkbox list for worker selection
- Helper buttons: Select All, Online Only, Clear All
- Sequential execution across selected workers
- Results summary showing success/fail count per worker
- Updated command history to track multi-worker executions
- Terminal beep feedback based on overall success/failure
- Maintained backward compatibility with single worker mode

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-07 23:03:45 -05:00
parent 095bfb65ab
commit 84edea8027
2 changed files with 519 additions and 214 deletions

477
README.md
View File

@@ -1,240 +1,375 @@
# PULSE - Pipelined Unified Logic & Server Engine # PULSE - Pipelined Unified Logic & Server Engine
A distributed workflow orchestration platform for managing and executing complex multi-step operations across server clusters through an intuitive web interface. A distributed workflow orchestration platform for managing and executing complex multi-step operations across server clusters through a retro terminal-themed web interface.
## Overview ## Overview
PULSE is a centralized workflow execution system designed to orchestrate operations across distributed infrastructure. It provides a powerful web-based interface for defining, managing, and executing workflows that can span multiple servers, require human interaction, and perform complex automation tasks at scale. PULSE is a centralized workflow execution system designed to orchestrate operations across distributed infrastructure. It provides a powerful web-based interface with a vintage CRT terminal aesthetic for defining, managing, and executing workflows that can span multiple servers, require human interaction, and perform complex automation tasks at scale.
### Key Features ### Key Features
- **Interactive Workflow Management**: Define and execute multi-step workflows with conditional logic, user prompts, and decision points - **🎨 Retro Terminal Interface**: Phosphor green CRT-style interface with scanlines, glow effects, and ASCII art
- **Distributed Execution**: Run commands and scripts across multiple worker nodes simultaneously - **⚡ Quick Command Execution**: Instantly execute commands on any worker with built-in templates and command history
- **High Availability Architecture**: Deploy redundant worker nodes in LXC containers with Ceph storage for fault tolerance - **📊 Real-Time Worker Monitoring**: Live system metrics including CPU, memory, load average, and active tasks
- **Web-Based Control Center**: Intuitive interface for workflow selection, monitoring, and interactive input - **🔄 Interactive Workflow Management**: Define and execute multi-step workflows with conditional logic and user prompts
- **Flexible Worker Pool**: Scale horizontally by adding worker nodes as needed - **🌐 Distributed Execution**: Run commands across multiple worker nodes simultaneously via WebSocket
- **Real-Time Monitoring**: Track workflow progress, view logs, and receive notifications - **📈 Execution Tracking**: Comprehensive logging with formatted output, re-run capabilities, and JSON export
- **🔐 SSO Authentication**: Seamless integration with Authelia for enterprise authentication
- **🧹 Auto-Cleanup**: Automatic removal of old executions with configurable retention policies
- **🔔 Terminal Notifications**: Audio beeps and visual toasts for command completion events
## Architecture ## Architecture
PULSE consists of two core components: PULSE consists of two core components:
### PULSE Server ### PULSE Server
**Location:** `10.10.10.65` (LXC Container ID: 122)
**Directory:** `/opt/pulse-server`
The central orchestration hub that: The central orchestration hub that:
- Hosts the web interface for workflow management - Hosts the retro terminal web interface
- Manages workflow definitions and execution state - Manages workflow definitions and execution state
- Coordinates task distribution to worker nodes - Coordinates task distribution to worker nodes via WebSocket
- Handles user interactions and input collection - Handles user interactions through Authelia SSO
- Provides real-time status updates and logging - Provides real-time status updates and logging
- Stores all data in MariaDB database
**Technology Stack:**
- Node.js 20.x
- Express.js (web framework)
- WebSocket (ws package) for real-time bidirectional communication
- MySQL2 (MariaDB driver)
- Authelia SSO integration
### PULSE Worker ### PULSE Worker
**Example:** `10.10.10.151` (LXC Container ID: 153, hostname: pulse-worker-01)
**Directory:** `/opt/pulse-worker`
Lightweight execution agents that: Lightweight execution agents that:
- Connect to the PULSE server and await task assignments - Connect to PULSE server via WebSocket with heartbeat monitoring
- Execute commands, scripts, and code on target infrastructure - Execute shell commands and report results in real-time
- Report execution status and results back to the server - Provide system metrics (CPU, memory, load, uptime)
- Support multiple concurrent workflow executions - Support concurrent task execution with configurable limits
- Automatically reconnect and resume on failure - Automatically reconnect on connection loss
**Technology Stack:**
- Node.js 20.x
- WebSocket client
- Child process execution
- System metrics collection
``` ```
┌─────────────────────┐ ┌─────────────────────────────────
│ PULSE Server │ PULSE Server (10.10.10.65)
(Web Interface) Terminal Web Interface + API
──────────────────── │ ┌───────────┐ ┌──────────┐ │
│ MariaDB │ │ Authelia │
┌──────┴───────┬──────────────┬──────────────┐ │ Database │ │ SSO │ │
│ │ │ │ └───────────┘ └──────────┘
───────┐ ┌───────┐ ┌───────┐ ┌─────── ────────────────────────────────
│ Worker │ │ Worker │ │ Worker │ │ Worker │ │ WebSocket
│ Node 1 │ │ Node 2 │ │ Node 3 │ │ Node N │ ┌────────┴────────┬───────────┐
└────────┘ └────────┘ └────────┘ └────────┘ │ │ │
LXC Containers in Proxmox with Ceph ┌───▼────────┐ ┌───▼────┐ ┌──▼─────┐
│ Worker 1 │ │Worker 2│ │Worker N│
│10.10.10.151│ │ ... │ │ ... │
└────────────┘ └────────┘ └────────┘
LXC Containers in Proxmox with Ceph
``` ```
## Deployment ## Installation
### Prerequisites ### Prerequisites
- **Proxmox VE Cluster**: Hypervisor environment for container deployment - **Node.js 20.x** or higher
- **Ceph Storage**: Distributed storage backend for high availability - **MariaDB 10.x** or higher
- **LXC Support**: Container runtime for worker node deployment - **Authelia** configured for SSO (optional but recommended)
- **Network Connectivity**: Communication between server and workers - **Network Connectivity** between server and workers
### Installation ### PULSE Server Setup
#### PULSE Server
```bash ```bash
# Clone the repository # Clone repository
git clone https://github.com/yourusername/pulse.git cd /opt
cd pulse git clone <your-repo-url> pulse-server
cd pulse-server
# Install dependencies # Install dependencies
npm install # or pip install -r requirements.txt npm install
# Configure server settings # Create .env file with configuration
cp config.example.yml config.yml cat > .env << EOF
nano config.yml # Server Configuration
PORT=8080
SECRET_KEY=your-secret-key-here
# Start the PULSE server # MariaDB Configuration
npm start # or python server.py DB_HOST=10.10.10.50
DB_PORT=3306
DB_NAME=pulse
DB_USER=pulse_user
DB_PASSWORD=your-db-password
# Worker API Key (for worker authentication)
WORKER_API_KEY=your-worker-api-key
# Auto-cleanup configuration (optional)
EXECUTION_RETENTION_DAYS=30
EOF
# Create systemd service
cat > /etc/systemd/system/pulse.service << EOF
[Unit]
Description=PULSE Workflow Orchestration Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/pulse-server
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# Start service
systemctl daemon-reload
systemctl enable pulse.service
systemctl start pulse.service
``` ```
#### PULSE Worker ### PULSE Worker Setup
```bash ```bash
# On each worker node (LXC container) # On each worker node
cd /opt
git clone <your-repo-url> pulse-worker
cd pulse-worker cd pulse-worker
# Install dependencies # Install dependencies
npm install # or pip install -r requirements.txt npm install
# Configure worker connection # Create .env file
cp worker-config.example.yml worker-config.yml cat > .env << EOF
nano worker-config.yml # Worker Configuration
WORKER_NAME=pulse-worker-01
PULSE_SERVER=http://10.10.10.65:8080
PULSE_WS=ws://10.10.10.65:8080
WORKER_API_KEY=your-worker-api-key
# Start the worker daemon # Performance Settings
npm start # or python worker.py HEARTBEAT_INTERVAL=30
``` MAX_CONCURRENT_TASKS=5
EOF
### High Availability Setup # Create systemd service
cat > /etc/systemd/system/pulse-worker.service << EOF
[Unit]
Description=PULSE Worker Node
After=network.target
Deploy multiple worker nodes across Proxmox hosts: [Service]
```bash Type=simple
# Create LXC template User=root
pct create 1000 local:vztmpl/ubuntu-22.04-standard_amd64.tar.zst \ WorkingDirectory=/opt/pulse-worker
--rootfs ceph-pool:8 \ ExecStart=/usr/bin/node worker.js
--memory 2048 \ Restart=always
--cores 2 \ RestartSec=10
--net0 name=eth0,bridge=vmbr0,ip=dhcp
# Clone for additional workers [Install]
pct clone 1000 1001 --full --storage ceph-pool WantedBy=multi-user.target
pct clone 1000 1002 --full --storage ceph-pool EOF
pct clone 1000 1003 --full --storage ceph-pool
# Start all workers # Start service
for i in {1000..1003}; do pct start $i; done systemctl daemon-reload
systemctl enable pulse-worker.service
systemctl start pulse-worker.service
``` ```
## Usage ## Usage
### Creating a Workflow ### Quick Command Execution
1. Access the PULSE web interface at `http://your-server:8080` 1. Access PULSE at `http://your-server:8080`
2. Navigate to **Workflows****Create New** 2. Navigate to **⚡ Quick Command** tab
3. Define workflow steps using the visual editor or YAML syntax 3. Select a worker from the dropdown
4. Specify execution targets (specific nodes, groups, or all workers) 4. Use **Templates** for pre-built commands or **History** for recent commands
5. Add interactive prompts where user input is required 5. Enter your command and click **Execute**
6. Save and activate the workflow 6. View results in the **Executions** tab
### Example Workflow **Built-in Command Templates:**
```yaml - System Info: `uname -a`
name: "System Update and Reboot" - Disk Usage: `df -h`
description: "Update all servers in the cluster with user confirmation" - Memory Usage: `free -h`
steps: - CPU Info: `lscpu`
- name: "Check Current Versions" - Running Processes: `ps aux --sort=-%mem | head -20`
type: "execute" - Network Interfaces: `ip addr show`
targets: ["all"] - Docker Containers: `docker ps -a`
command: "apt list --upgradable" - System Logs: `tail -n 50 /var/log/syslog`
- name: "User Approval"
type: "prompt"
message: "Review available updates. Proceed with installation?"
options: ["Yes", "No", "Cancel"]
- name: "Install Updates"
type: "execute"
targets: ["all"]
command: "apt-get update && apt-get upgrade -y"
condition: "prompt_response == 'Yes'"
- name: "Reboot Confirmation"
type: "prompt"
message: "Updates complete. Reboot all servers?"
options: ["Yes", "No"]
- name: "Rolling Reboot"
type: "execute"
targets: ["all"]
command: "reboot"
strategy: "rolling"
condition: "prompt_response == 'Yes'"
```
### Running a Workflow ### Worker Monitoring
1. Select a workflow from the dashboard The **Workers** tab displays real-time metrics for each worker:
2. Click **Execute** - System information (OS, architecture, CPU cores)
3. Monitor progress in real-time - Memory usage (used/total with percentage)
4. Respond to interactive prompts as they appear - Load averages (1m, 5m, 15m)
5. View detailed logs for each execution step - System uptime
- Active tasks vs. maximum concurrent capacity
## Configuration ### Execution Management
### Server Configuration (`config.yml`) - **View Details**: Click any execution to see formatted logs with timestamps, status, and output
```yaml - **Re-run Command**: Click "Re-run" button in execution details to repeat a command
server: - **Download Logs**: Export execution data as JSON for auditing
host: "0.0.0.0" - **Clear Completed**: Bulk delete finished executions
port: 8080 - **Auto-Cleanup**: Executions older than 30 days are automatically removed
secret_key: "your-secret-key"
database: ### Workflow Creation (Future Feature)
type: "postgresql"
host: "localhost"
port: 5432
name: "pulse"
workers: 1. Navigate to **Workflows****Create New**
heartbeat_interval: 30 2. Define workflow steps using JSON syntax
timeout: 300 3. Specify target workers
max_concurrent_tasks: 10 4. Add interactive prompts where needed
5. Save and execute
security:
enable_authentication: true
require_approval: true
```
### Worker Configuration (`worker-config.yml`)
```yaml
worker:
name: "worker-01"
server_url: "http://pulse-server:8080"
api_key: "worker-api-key"
resources:
max_cpu_percent: 80
max_memory_mb: 1024
executor:
shell: "/bin/bash"
working_directory: "/tmp/pulse"
timeout: 3600
```
## Features in Detail ## Features in Detail
### Interactive Workflows ### Terminal Aesthetic
- Pause execution to collect user input via web forms - Phosphor green (#00ff41) on black (#0a0a0a) color scheme
- Display intermediate results for review - CRT scanline animation effect
- Conditional branching based on user decisions - Text glow and shadow effects
- Multi-choice prompts with validation - ASCII box-drawing characters for borders
- Boot sequence animation on first load
- Hover effects with smooth transitions
### Mass Execution ### Real-Time Communication
- Run commands across all workers simultaneously - WebSocket-based bidirectional communication
- Target specific node groups or individual servers - Instant command result notifications
- Rolling execution for zero-downtime updates - Live worker status updates
- Parallel and sequential execution strategies - Terminal beep sounds for events
- Toast notifications with visual feedback
### Monitoring & Logging ### Execution Tracking
- Real-time workflow execution dashboard - Formatted log display (not raw JSON)
- Detailed per-step logging and output capture - Color-coded success/failure indicators
- Historical execution records and analytics - Timestamp and duration for each step
- Alert notifications for failures or completion - Scrollable output with syntax highlighting
- Persistent history with pagination
- Load More button for large execution lists
### Security ### Security
- Role-based access control (RBAC) - Authelia SSO integration for user authentication
- API key authentication for workers - API key authentication for workers
- Workflow approval requirements - User session management
- Audit logging for all actions - Admin-only operations (worker deletion, workflow management)
- Audit logging for all executions
### Performance
- Automatic cleanup of old executions (configurable retention)
- Pagination for large execution lists (50 at a time)
- Efficient WebSocket connection pooling
- Worker heartbeat monitoring
- Database connection pooling
## Configuration
### Environment Variables
**Server (.env):**
```bash
PORT=8080 # Server port
SECRET_KEY=<random-string> # Session secret
DB_HOST=10.10.10.50 # MariaDB host
DB_PORT=3306 # MariaDB port
DB_NAME=pulse # Database name
DB_USER=pulse_user # Database user
DB_PASSWORD=<password> # Database password
WORKER_API_KEY=<api-key> # Worker authentication key
EXECUTION_RETENTION_DAYS=30 # Auto-cleanup retention (default: 30)
```
**Worker (.env):**
```bash
WORKER_NAME=pulse-worker-01 # Unique worker name
PULSE_SERVER=http://10.10.10.65:8080 # Server HTTP URL
PULSE_WS=ws://10.10.10.65:8080 # Server WebSocket URL
WORKER_API_KEY=<api-key> # Must match server key
HEARTBEAT_INTERVAL=30 # Heartbeat seconds (default: 30)
MAX_CONCURRENT_TASKS=5 # Max parallel tasks (default: 5)
```
## Database Schema
PULSE uses MariaDB with the following tables:
- **users**: User accounts from Authelia SSO
- **workers**: Worker node registry with metadata
- **workflows**: Workflow definitions (JSON)
- **executions**: Execution history with logs
See [Claude.md](Claude.md) for complete schema details.
## Troubleshooting
### Worker Not Connecting
```bash
# Check worker service status
systemctl status pulse-worker
# Check worker logs
journalctl -u pulse-worker -n 50 -f
# Verify API key matches server
grep WORKER_API_KEY /opt/pulse-worker/.env
```
### Commands Stuck in "Running"
- This was fixed in recent updates - restart the server:
```bash
systemctl restart pulse.service
```
### Clear All Executions
Use the database directly if needed:
```bash
mysql -h 10.10.10.50 -u pulse_user -p pulse
> DELETE FROM executions WHERE status IN ('completed', 'failed');
```
## Development
### Recent Updates
**Phase 1-6 Improvements:**
- Formatted log display with color-coding
- Worker system metrics monitoring
- Command templates and history
- Re-run and download execution features
- Auto-cleanup and pagination
- Terminal aesthetic refinements
- Audio notifications and visual toasts
See git history for detailed changelog.
### Future Enhancements
- Full workflow system implementation
- Multi-worker command execution
- Scheduled/cron job support
- Execution search and filtering
- Dark/light theme toggle
- Mobile-responsive design
- REST API documentation
- Webhook integrations
## License
MIT License - See LICENSE file for details
--- ---
**PULSE** - Orchestrating your infrastructure, one heartbeat at a time. **PULSE** - Orchestrating your infrastructure, one heartbeat at a time.
Built with retro terminal aesthetics 🖥️ | Powered by WebSockets 🔌 | Secured by Authelia 🔐

View File

@@ -826,10 +826,36 @@
<button onclick="showCommandHistory()" style="flex: 0;">[ 🕐 History ]</button> <button onclick="showCommandHistory()" style="flex: 0;">[ 🕐 History ]</button>
</div> </div>
<label style="display: block; margin-bottom: 10px; font-weight: 600;">Select Worker:</label> <label style="display: block; margin-bottom: 10px; font-weight: 600;">Execution Mode:</label>
<select id="quickWorkerSelect"> <div style="margin-bottom: 20px;">
<option value="">Loading workers...</option> <label style="display: inline-flex; align-items: center; margin-right: 20px; cursor: pointer;">
</select> <input type="radio" name="execMode" value="single" checked onchange="toggleWorkerSelection()" style="width: auto; margin-right: 8px;">
<span>Single Worker</span>
</label>
<label style="display: inline-flex; align-items: center; cursor: pointer;">
<input type="radio" name="execMode" value="multi" onchange="toggleWorkerSelection()" style="width: auto; margin-right: 8px;">
<span>Multiple Workers</span>
</label>
</div>
<div id="singleWorkerMode">
<label style="display: block; margin-bottom: 10px; font-weight: 600;">Select Worker:</label>
<select id="quickWorkerSelect">
<option value="">Loading workers...</option>
</select>
</div>
<div id="multiWorkerMode" style="display: none;">
<label style="display: block; margin-bottom: 10px; font-weight: 600;">Select Workers:</label>
<div id="workerCheckboxList" style="background: var(--bg-primary); border: 2px solid var(--terminal-green); padding: 15px; margin-bottom: 15px; max-height: 200px; overflow-y: auto;">
<div class="loading">Loading workers...</div>
</div>
<div style="margin-bottom: 15px;">
<button onclick="selectAllWorkers()" class="small">[ Select All ]</button>
<button onclick="selectOnlineWorkers()" class="small">[ Online Only ]</button>
<button onclick="deselectAllWorkers()" class="small">[ Clear All ]</button>
</div>
</div>
<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>
@@ -919,14 +945,28 @@
try { try {
const response = await fetch('/api/workers'); const response = await fetch('/api/workers');
workers = await response.json(); workers = await response.json();
// Update worker select in quick command // Update worker select in quick command (single mode)
const select = document.getElementById('quickWorkerSelect'); const select = document.getElementById('quickWorkerSelect');
if (select) { if (select) {
select.innerHTML = workers.map(w => select.innerHTML = workers.map(w =>
`<option value="${w.id}">${w.name} (${w.status})</option>` `<option value="${w.id}">${w.name} (${w.status})</option>`
).join(''); ).join('');
} }
// Update worker checkboxes (multi mode)
const checkboxList = document.getElementById('workerCheckboxList');
if (checkboxList) {
checkboxList.innerHTML = workers.length === 0 ?
'<div class="empty">No workers available</div>' :
workers.map(w => `
<label style="display: block; margin-bottom: 10px; cursor: pointer; padding: 8px; border: 1px solid var(--terminal-green); background: ${w.status === 'online' ? 'rgba(0, 255, 65, 0.05)' : 'transparent'};">
<input type="checkbox" name="workerCheckbox" value="${w.id}" data-status="${w.status}" style="width: auto; margin-right: 8px;">
<span class="status ${w.status}" style="padding: 2px 8px; font-size: 0.8em;">[${w.status === 'online' ? '●' : '○'}]</span>
<strong>${w.name}</strong>
</label>
`).join('');
}
// Dashboard view // Dashboard view
const dashHtml = workers.length === 0 ? const dashHtml = workers.length === 0 ?
@@ -1416,6 +1456,38 @@
localStorage.setItem('commandHistory', JSON.stringify(history)); localStorage.setItem('commandHistory', JSON.stringify(history));
} }
function toggleWorkerSelection() {
const mode = document.querySelector('input[name="execMode"]:checked').value;
const singleMode = document.getElementById('singleWorkerMode');
const multiMode = document.getElementById('multiWorkerMode');
if (mode === 'single') {
singleMode.style.display = 'block';
multiMode.style.display = 'none';
} else {
singleMode.style.display = 'none';
multiMode.style.display = 'block';
}
}
function selectAllWorkers() {
document.querySelectorAll('input[name="workerCheckbox"]').forEach(cb => {
cb.checked = true;
});
}
function selectOnlineWorkers() {
document.querySelectorAll('input[name="workerCheckbox"]').forEach(cb => {
cb.checked = cb.getAttribute('data-status') === 'online';
});
}
function deselectAllWorkers() {
document.querySelectorAll('input[name="workerCheckbox"]').forEach(cb => {
cb.checked = false;
});
}
async function deleteWorker(workerId, name) { async function deleteWorker(workerId, name) {
if (!confirm(`Delete worker: ${name}?`)) return; if (!confirm(`Delete worker: ${name}?`)) return;
@@ -1495,51 +1567,149 @@
} }
async function executeQuickCommand() { async function executeQuickCommand() {
const workerId = document.getElementById('quickWorkerSelect').value;
const command = document.getElementById('quickCommand').value; const command = document.getElementById('quickCommand').value;
const execMode = document.querySelector('input[name="execMode"]:checked').value;
if (!workerId || !command) { if (!command) {
alert('Please select a worker and enter a command'); alert('Please 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>';
try { if (execMode === 'single') {
const response = await fetch(`/api/workers/${workerId}/command`, { // Single worker execution
method: 'POST', const workerId = document.getElementById('quickWorkerSelect').value;
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command })
});
if (response.ok) { if (!workerId) {
const data = await response.json(); alert('Please select a worker');
return;
// Add to command history }
addToCommandHistory(command, workerName);
const worker = workers.find(w => w.id === workerId);
resultDiv.innerHTML = ` const workerName = worker ? worker.name : 'Unknown';
<div style="background: #001a00; border: 2px solid var(--terminal-green); padding: 15px;">
<strong style="color: var(--terminal-green);">✓ Command sent successfully!</strong> resultDiv.innerHTML = '<div class="loading">Executing command...</div>';
<div style="margin-top: 10px; font-family: var(--font-mono); font-size: 0.9em; color: var(--terminal-green);">
Execution ID: ${data.execution_id} try {
</div> const response = await fetch(`/api/workers/${workerId}/command`, {
<div style="margin-top: 10px; color: var(--terminal-amber);"> method: 'POST',
Check the Executions tab to see the results headers: { 'Content-Type': 'application/json' },
</div> body: JSON.stringify({ command })
</div> });
`;
} else { if (response.ok) {
resultDiv.innerHTML = '<div style="color: #ef4444;">Failed to execute command</div>'; const data = await response.json();
addToCommandHistory(command, workerName);
resultDiv.innerHTML = `
<div style="background: #001a00; border: 2px solid var(--terminal-green); padding: 15px;">
<strong style="color: var(--terminal-green);">✓ Command sent successfully!</strong>
<div style="margin-top: 10px; font-family: var(--font-mono); font-size: 0.9em; color: var(--terminal-green);">
Execution ID: ${data.execution_id}
</div>
<div style="margin-top: 10px; color: var(--terminal-amber);">
Check the Executions tab to see the results
</div>
</div>
`;
terminalBeep('success');
} else {
resultDiv.innerHTML = '<div style="color: #ef4444;">Failed to execute command</div>';
terminalBeep('error');
}
} catch (error) {
console.error('Error executing command:', error);
resultDiv.innerHTML = '<div style="color: #ef4444;">Error: ' + error.message + '</div>';
terminalBeep('error');
}
} else {
// Multi-worker execution
const selectedCheckboxes = document.querySelectorAll('input[name="workerCheckbox"]:checked');
const selectedWorkerIds = Array.from(selectedCheckboxes).map(cb => cb.value);
if (selectedWorkerIds.length === 0) {
alert('Please select at least one worker');
return;
}
resultDiv.innerHTML = `<div class="loading">Executing command on ${selectedWorkerIds.length} worker(s)...</div>`;
const results = [];
let successCount = 0;
let failCount = 0;
for (const workerId of selectedWorkerIds) {
try {
const worker = workers.find(w => w.id === workerId);
const response = await fetch(`/api/workers/${workerId}/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command })
});
if (response.ok) {
const data = await response.json();
results.push({
worker: worker.name,
success: true,
executionId: data.execution_id
});
successCount++;
} else {
results.push({
worker: worker.name,
success: false,
error: 'Failed to execute'
});
failCount++;
}
} catch (error) {
const worker = workers.find(w => w.id === workerId);
results.push({
worker: worker ? worker.name : workerId,
success: false,
error: error.message
});
failCount++;
}
}
// Add to history with multi-worker notation
addToCommandHistory(command, `${selectedWorkerIds.length} workers`);
// Display results summary
resultDiv.innerHTML = `
<div style="background: #001a00; border: 2px solid var(--terminal-green); padding: 15px;">
<strong style="color: var(--terminal-amber);">Multi-Worker Execution Complete</strong>
<div style="margin-top: 10px; font-family: var(--font-mono); font-size: 0.9em;">
<span style="color: var(--terminal-green);">✓ Success: ${successCount}</span> |
<span style="color: #ef4444;">✗ Failed: ${failCount}</span>
</div>
<div style="margin-top: 15px; max-height: 300px; overflow-y: auto;">
${results.map(r => `
<div style="margin-bottom: 8px; padding: 8px; border-left: 3px solid ${r.success ? 'var(--terminal-green)' : '#ef4444'}; background: rgba(0, 0, 0, 0.5);">
<strong>${r.worker}</strong>:
${r.success ?
`<span style="color: var(--terminal-green);">✓ Sent (ID: ${r.executionId.substring(0, 8)}...)</span>` :
`<span style="color: #ef4444;">✗ ${r.error}</span>`
}
</div>
`).join('')}
</div>
<div style="margin-top: 15px; color: var(--terminal-amber);">
Check the Executions tab to see detailed results
</div>
</div>
`;
if (failCount === 0) {
terminalBeep('success');
} else if (successCount > 0) {
terminalBeep('info');
} else {
terminalBeep('error');
} }
} catch (error) {
console.error('Error executing command:', error);
resultDiv.innerHTML = '<div style="color: #ef4444;">Error: ' + error.message + '</div>';
} }
} }