# VM Fleet Dashboard

A real-time web dashboard for monitoring VirtualBox VMs managed by the MCP VM Blackbox server.

## Architecture

```
dashboard/
├── client/          # React + Vite frontend
│   ├── src/
│   │   ├── components/   # Reusable UI components
│   │   ├── pages/        # Route-level page components
│   │   ├── lib/          # API client, types, utilities
│   │   └── index.css     # Tailwind + custom styles
│   └── dist/              # Production build output
└── server/          # FastAPI backend
    └── vm_dashboard/
        ├── app.py         # FastAPI application
        ├── store.py       # SQLite-backed fleet data reader (FleetStore)
        ├── db.py          # Async DB access helpers (aiosqlite queries)
        └── seed.py        # Seed data generator
```

### Data Flow

1. **MCP Server** writes VM registry and job data to `.mcp-vm-blackbox/`
2. **Dashboard Server** reads files directly from `.mcp-vm-blackbox/` (no MCP protocol)
3. **WebSocket** pushes fleet updates to connected clients
4. **React Client** renders fleet grid, VM details, and activity feed

## Quick Start

```bash
# 1. Install dashboard dependencies
uv sync --extra dashboard

# 2. Seed demo data (4 VMs, 4 jobs, screenshots)
uv run seed-dashboard

# 3. Build the React client
cd dashboard/client && npm install && npm run build

# 4. Start the dashboard server
uv run vm-dashboard

# 5. Open http://localhost:4821
```

## API Endpoints

| Endpoint                                | Method    | Description                                 |
| --------------------------------------- | --------- | ------------------------------------------- |
| `/api/fleet`                            | GET       | List all VMs with job summaries             |
| `/api/vms`                              | GET       | List all VMs from registry                  |
| `/api/vms/{vm_name}/screenshots/latest` | GET       | Latest screenshot for VM                    |
| `/api/stats`                            | GET       | Aggregate fleet statistics                  |
| `/api/timeline`                         | GET       | Fleet-level interleaved timeline            |
| `/api/jobs`                             | GET       | List all jobs (optional `?vm_name=` filter) |
| `/api/jobs/{job_id}`                    | GET       | Get single job record with step count       |
| `/api/jobs/{job_id}/steps`              | GET       | Paginated step log                          |
| `/api/jobs/{job_id}/issues`             | GET       | Issue list for a job                        |
| `/api/jobs/{job_id}/steer`              | POST      | Send a steering message to a pilot          |
| `/api/jobs/{job_id}/todo`               | GET       | TODO list for a job                         |
| `/api/jobs/{job_id}/todo`               | POST      | Add a TODO item                             |
| `/api/jobs/{job_id}/todo/{item_id}`     | PATCH     | Update a TODO item                          |
| `/api/jobs/{job_id}/todo/reorder`       | PUT       | Reorder TODO items by ordered_ids           |
| `/api/video/{vm_name}/{job_id}`         | GET       | Job recording video                         |
| `/api/events/ingest`                    | POST      | Webhook for MCP tool event push             |
| `/api/an-token`                         | POST      | Anthropic token endpoint                    |
| `/ws`                                   | WebSocket | Real-time fleet updates                     |

### Example Responses

**GET /api/fleet**

```json
[
  {
    "vm_name": "win11-dsr-prod",
    "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "running": true,
    "last_refreshed": "2024-01-15T10:30:00Z",
    "jobs": [
      {
        "job_id": "a1b2c3d4e5f6",
        "scenario_name": "dsr-install",
        "status": "active",
        "last_agent_id": "vm-pilot:5678",
        "last_step": "click-next-installer-page-3",
        "updated_at": "2024-01-15T10:25:00Z",
        "pilot_context_usage": 0.65
      }
    ],
    "active_job_count": 1,
    "total_job_count": 1
  }
]
```

**GET /api/stats**

```json
{
  "total_vms": 4,
  "running_vms": 2,
  "total_jobs": 4,
  "active_jobs": 1,
  "paused_jobs": 1,
  "completed_jobs": 1,
  "failed_jobs": 1,
  "ws_connections": 2
}
```

## Seed Data

Run `uv run seed-dashboard` to create demo data:

- **4 VMs**: `win11-dsr-prod`, `ubuntu-22-staging`, `macos-14-build`, `win10-legacy-test`
- **4 Jobs**: active, paused, completed, failed (one each)
- **4 Screenshots**: Placeholder PNGs with grid pattern

Seed files are written to:

- `.mcp-vm-blackbox/registry/vms.json`
- `.mcp-vm-blackbox/jobs/{vm_name}/{job_id}.json`
- `scratch/screenshots/{vm_name}-{timestamp}-seed0001.png`

## Development Workflow

### Frontend Development

```bash
cd dashboard/client

# Install dependencies
npm install

# Start Vite dev server (hot reload)
npm run dev

# Type check
npm run check

# Build for production
npm run build
```

### Backend Development

```bash
# Run with auto-reload
uvicorn vm_dashboard.app:app --reload --port 4821

# Run tests
uv run pytest packages/mcp_vm_blackbox/tests/ --no-cov
```

### File Watching

The dashboard server uses `watchfiles` to monitor `.mcp-vm-blackbox/` for changes. When files change, all connected WebSocket clients receive the updated fleet data.

## Responsive Design

The dashboard adapts to viewport sizes:

| Breakpoint       | Fleet Grid          | Activity Sidebar |
| ---------------- | ------------------- | ---------------- |
| 375px (mobile)   | 1 column            | Hidden           |
| 768px (tablet)   | 2 columns           | Hidden           |
| 1024px (laptop)  | 2 columns           | Hidden           |
| 1440px (desktop) | 3 columns           | Visible          |
| 1536px (xl)      | 3 columns + sidebar | Visible          |

## Styling

- **CSS Variables**: Theme colors defined in `index.css` (`--color-accent`, `--color-bg`, etc.)
- **Tailwind**: Utility classes for layout and spacing
- **Dark Theme**: Default theme with accent color `#22C55E` (green)

## Troubleshooting

### Dashboard shows "No VMs found"

1. Check that the coordination database exists: `ls .mcp-vm-blackbox/coordination.db`
2. Run seed if missing: `uv run seed-dashboard`
3. Confirm at least one VM has been registered via `vm_list` so the `vm_registry` table is populated
4. Refresh the browser

### WebSocket not connecting

1. Check server logs for WebSocket errors
2. Verify port 4821 is not in use by another process
3. Try a hard refresh (Ctrl+Shift+R)

### Build fails with TypeScript errors

1. Run `npm run check` to see detailed errors
2. Ensure all imports use correct paths (relative imports for components)
3. Check that `api.ts` functions match `types.ts` definitions

## License

MIT
