Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e1b0cae
docs: add serial command execution plan
Gerrri Mar 25, 2026
b64bd04
feat(backend): add preset-aware serial command execution
Gerrri Mar 25, 2026
ee6cc74
test(staging): configure raw serial console transport
Gerrri Mar 25, 2026
732b45f
feat(frontend): surface per-target command capability
Gerrri Mar 25, 2026
12c9f5a
docs: update root readme for serial command execution
Gerrri Mar 25, 2026
7af4177
chore(release): bump version to 0.1.4
Gerrri Mar 25, 2026
4d0ddaf
fix(runtime): prefer image app version at runtime
Gerrri Mar 26, 2026
c4cb352
feat(backend): add exporter ssh bundle execution support
Gerrri Mar 26, 2026
1846adf
test(staging): add four-exporter transport matrix
Gerrri Mar 26, 2026
579e872
feat(frontend): show actual command transport
Gerrri Mar 26, 2026
fc4b1f3
docs: update exporter ssh and staging guidance
Gerrri Mar 26, 2026
c3bbe4a
refactor(frontend): simplify command panel chrome
Gerrri Mar 26, 2026
7b4dd91
refactor(frontend): move clear action into terminal header
Gerrri Mar 26, 2026
3b142cd
refactor(frontend): refine preset headers and table alignment
Gerrri Mar 26, 2026
a213821
chore(release): bump version to 0.1.5
Gerrri Mar 26, 2026
fc7df18
fix(backend): address review findings for scheduling and resources
Gerrri Mar 26, 2026
241b63a
chore(release): bump version to 0.1.6
Gerrri Mar 26, 2026
2bf2406
fix(backend): validate scheduled commands and resolve wildcard matches
Gerrri Mar 26, 2026
22f0537
fix(frontend): stabilize staging dev server connections
Gerrri Mar 26, 2026
ed1c9c7
chore(release): bump version to 0.1.7
Gerrri Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile.prod
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ ENV PYTHONUNBUFFERED=1
ENV UVICORN_WORKERS=1
ENV UVICORN_LOG_LEVEL=info
ENV APP_VERSION=${APP_VERSION}
ENV IMAGE_APP_VERSION=${APP_VERSION}

# Copy frontend build artifacts to nginx web root
COPY --from=frontend-builder /app/frontend/dist /var/www/html
Expand Down
152 changes: 121 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ Labgrid Dashboard provides a real-time web interface to:
- **Monitor status** - See which devices are available, acquired, or offline
- **Track ownership** - Know who currently has acquired each exporter/target
- **Quick access** - Click on IP addresses to directly access device web interfaces
- **Execute commands** - Run predefined commands on DUTs and view their outputs
- **Execute commands** - Run predefined commands on DUTs with serial-first transport, exporter SSH bundles, and SSH fallback
- **Hardware Presets** - Assign hardware-specific command sets to different targets
- **Grouped Display** - Targets are automatically grouped by their preset type
- **Transport-aware UI** - Hide command controls on unsupported targets, show a per-device reason, and surface the transport actually used for each command
- **Real-time updates** - WebSocket-based live status updates without manual refresh

> 📖 For a quick introduction, see the [Quick Start Guide](quick-start.md).
Expand Down Expand Up @@ -59,7 +60,7 @@ Labgrid Dashboard provides a real-time web interface to:
docker pull ghcr.io/gerrri/labgrid-dashboard:latest

# Or pin to a specific version (recommended for production)
docker pull ghcr.io/gerrri/labgrid-dashboard:1.0.0
docker pull ghcr.io/gerrri/labgrid-dashboard:v0.1.4
```

### Quick Start
Expand All @@ -69,6 +70,7 @@ docker run -d \
--name labgrid-dashboard \
-p 80:80 \
-e COORDINATOR_URL=ws://your-coordinator:20408/ws \
-v ./exporter-ssh:/app/exporter-ssh:ro \
ghcr.io/gerrri/labgrid-dashboard:latest
```

Expand Down Expand Up @@ -123,6 +125,38 @@ The frontend normalizes runtime URL settings to avoid malformed paths:
- `API_URL`: `""`, `/`, `/api`, `/api/` all resolve correctly (no `/api/api/*`)
- `WS_URL`: relative and absolute values are normalized to a valid WebSocket URL

### Exporter SSH Bundles

Serial command execution still reaches exporters over SSH. The backend expects an exporter SSH bundle tree at runtime and generates the SSH material it needs from that input.

Runtime input path:

- `/app/exporter-ssh/<exporter-name>/exporter.yaml`
- optional key files in the same exporter directory, such as `/app/exporter-ssh/<exporter-name>/id_ed25519`

Generated runtime SSH material:

- `~/.ssh/config` with an include for the managed exporter config
- `~/.ssh/labgrid-dashboard/config`
- `~/.ssh/labgrid-dashboard/known_hosts`
- `~/.ssh/labgrid-dashboard/keys/<exporter-name>` when a private key is provided

Supported auth modes:

- private key
- username/password

Example bundle layout:

```text
/app/exporter-ssh/
exporter-1/
exporter.yaml
id_ed25519
exporter-2/
exporter.yaml
```

### Example: Production with Docker Compose

```yaml
Expand All @@ -138,6 +172,7 @@ services:
volumes:
- ./commands.yaml:/app/commands.yaml:ro
- ./target_presets.json:/app/target_presets.json:ro
- ./exporter-ssh:/app/exporter-ssh:ro
restart: unless-stopped
```

Expand All @@ -163,7 +198,7 @@ docker compose up -d
- Backend API: http://localhost:8000

### Staging Mode
Runs with simulated DUTs (Alpine Linux containers) and real Labgrid Exporters. Commands are executed via `labgrid-client` CLI, which properly routes through: Backend → Coordinator → Exporter → DUT.
Runs with simulated DUTs (Alpine Linux containers) and real Labgrid exporters. Commands prefer a serial shell and still reach exporters over SSH through the exporter bundle configuration. If serial execution is not available for the target/preset, the backend falls back to SSH when the preset allows it.

If the backend starts before the coordinator becomes reachable, it remains available in degraded mode and retries the coordinator connection automatically in the background.

Expand All @@ -174,31 +209,36 @@ docker compose --profile staging up -d --build

**Ports**: Same as development mode

**Auto-Acquire Feature:**
When starting in staging mode, an init-container automatically:
1. Creates a place named `exporter-1`
2. Matches it with the exporter-1 resources
3. Acquires the place as `staging-user`
**Staging Topology:**
The staging profile creates four places and leaves all of them idle by default:

- `exporter-1`: serial-only command execution
- `exporter-2`: serial-first command execution with SSH fallback
- `exporter-3`: SSH command execution using a DUT private key
- `exporter-4`: SSH command execution using DUT username/password

The exporter SSH bundle coverage in staging also exercises both supported exporter auth modes:

This demonstrates the "acquired" status in the dashboard with:
- `exporter-1`: Status "acquired", acquired_by: "staging-user"
- `exporter-2`, `exporter-3`: Status "available"
- `exporter-1`: exporter reached through a private key bundle
- `exporter-4`: exporter reached through a username/password bundle

After running a command, the UI updates the `Command transport:` label to the transport that was actually used for the latest execution. For example, `exporter-2` switches from `serial` to `ssh` after the SSH fallback path succeeds.

**Staging Architecture:**
```
┌─────────────────────────────────────────────────────────────┐
│ Staging Environment │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ DUT-1 │ │ DUT-2 │ │ DUT-3 │ (Alpine Linux)
│ │ :5000 │ │ :5000 │ │ :5000 │
│ └────┬────┘ └────┬────┘ └────┬────┘
│ │ Serial │ Serial │ Serial
│ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ │Exporter1│ │Exporter2│ │Exporter3│ (labgrid)
│ └────┬────┘ └────┬────┘ └────┬────┘
│ └──────────┬──┴─────────────┘ gRPC
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ DUT-1 │ │ DUT-2 │ │ DUT-3 │ │ DUT-4 │
│ │ :5000 │ │ :5000 │ │ :5000 │ │ :5000
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ Serial │ Serial │ SSH │ SSH
│ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ │Exporter1│ │Exporter2│ │Exporter3│ │Exporter4│
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ └─────────────┴─────────────┴─────────────┘ gRPC │
│ ┌─────▼─────┐ │
│ │Coordinator│ (labgrid 24.0+) │
│ └─────┬─────┘ │
Expand All @@ -215,17 +255,56 @@ This demonstrates the "acquired" status in the dashboard with:

**How Command Execution Works:**
1. Frontend sends command request to Backend via HTTP
2. Backend uses `labgrid-client` CLI to execute commands
3. `labgrid-client` communicates with Coordinator via gRPC
4. Coordinator routes to appropriate Exporter
5. Exporter uses the appropriate driver (ShellDriver, SSHDriver, etc.) to execute on DUT
6. Output flows back through the same path

**Supported Connection Types:**
Labgrid automatically selects the appropriate driver based on available resources:
- **NetworkSerialPort** - Serial over TCP (used in staging)
- **USBSerialPort** - Direct USB serial connection
- **SSHDriver** - SSH connection for network-accessible DUTs
2. Backend resolves the target's preset and `command_execution` transport order
3. Backend prefers serial execution through Labgrid's `SerialDriver` + `ShellDriver`
4. Serial command execution still reaches the exporter over SSH via the exporter bundle configuration
5. If serial is unavailable or transport setup fails, Backend falls back to the backend-managed `SSHDriver` when SSH is allowed by the preset
6. The frontend shows the transport that was actually used for the latest command result
7. Scheduled commands, REST requests, and WebSocket-triggered commands all use the same backend execution service
8. Output flows back through the same path

**Supported Execution Transports:**
- **Serial shell** - Uses a `NetworkSerialPort` and Labgrid's `ShellDriver`, including login automation and prompt detection
- **Exporter SSH bundles** - Provide exporter host/IP, `known_hosts`, and optional key material for serial transport
- **SSH fallback** - Uses the backend-managed `SSHDriver` when a `NetworkService` is available and the preset allows SSH
- **Unsupported** - If neither transport is available, the backend marks the target as not command-capable and the UI hides command controls for that device

### Command Execution Configuration

Command execution is configured per preset in `backend/commands.yaml`. Each preset can define:

- ordered transport preference
- serial login automation
- shell prompt detection
- serial command timeout overrides

Example:

```yaml
default_preset: basic

presets:
basic:
name: "Basic"
description: "Standard Linux Commands"
command_execution:
transport_order:
- serial
- ssh
serial:
prompt: ".*[#\\$] "
login_prompt: "(?i)login: ?"
username: "root"
password_env: "LABGRID_SERIAL_PASSWORD"
command_timeout_seconds: 60
```

Notes:

- `transport_order` is evaluated from left to right per target
- `serial.username` / `serial.password` can be set inline, or via `serial.username_env` / `serial.password_env`
- the same transport order is used for manual commands and scheduled commands
- the UI uses backend-provided command capability metadata, so unsupported targets do not show command buttons

## Docker Commands

Expand Down Expand Up @@ -330,6 +409,10 @@ presets:
interval_seconds: 30
```

Scheduled command names must be unique within each preset. The scheduler uses the
display name as the column key in the UI, so duplicate names inside the same
preset are rejected during configuration loading.

**Preset Assignment:**
- Targets are assigned to presets via the Settings icon (⚙️) in the expanded target view
- Assignments are stored in `target_presets.json`
Expand Down Expand Up @@ -481,4 +564,11 @@ Please review the [AGENTS.md](AGENTS.md) and [agent-rules/](agent-rules/) for co

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
free to submit issues and pull requests.

Please review the [AGENTS.md](AGENTS.md) and [agent-rules/](agent-rules/) for coding guidelines when contributing.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
25 changes: 25 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ docker run -d \
--name labgrid-test \
-p 8080:80 \
-e COORDINATOR_URL=ws://your-coordinator:20408/ws \
-v ./exporter-ssh:/app/exporter-ssh:ro \
labgrid-dashboard:test

# Test endpoints
Expand All @@ -167,6 +168,26 @@ curl http://localhost:8080/api/health # Backend (requires coordinator)
curl http://localhost:8080/env-config.js # Runtime config
```

## Exporter SSH Bundle Validation

Use the staging profile to verify both supported exporter auth modes:

```bash
docker compose --profile staging up -d --build
```

Expected checks:
- `exporter-1` executes commands over serial
- `exporter-2` tries serial first and falls back to SSH
- `exporter-3` executes commands over SSH using a DUT private key
- `exporter-4` executes commands over SSH using DUT username/password
- all four exporters start as `available` with no default acquisition after the coordinator cache settles
- `~/.ssh/config` includes the managed exporter SSH snippet
- `~/.ssh/labgrid-dashboard/config`, `~/.ssh/labgrid-dashboard/known_hosts`, and generated key files are present in the runtime container
- serial command execution still reaches exporters over SSH before falling back to DUT SSH
- the UI updates `Command transport:` to the transport actually used for the latest execution
- after a reload, `/api/targets` still provides `last_command_outputs[*].execution_transport` for the newest manual command result

## Regression Testing

After all fixes, verify these still work:
Expand All @@ -176,6 +197,9 @@ After all fixes, verify these still work:
- ✅ WebSocket real-time updates
- ✅ Scheduled command columns
- ✅ Target settings dialog
- ✅ Exporter SSH bundle loading
- ✅ Serial command execution through exporter SSH for both private key and username/password auth
- ✅ Actual transport display persists via cached manual command outputs

## Automated Test Summary

Expand All @@ -184,6 +208,7 @@ After all fixes, verify these still work:
| Frontend Build | ✅ PASS | TypeScript compilation successful |
| Backend Tests | ✅ PASS | 132/133 tests passed |
| Production Image | ✅ PASS | Docker build & runtime tests passed |
| Exporter SSH Bundles | ✅ PASS | Staging exercises private key and username/password exporter bundles |
| Manual Testing | ⏳ TODO | Follow guide above |

## Known Issues (Pre-existing)
Expand Down
Loading
Loading