Features: - Web Dashboard: FastAPI-based dashboard with Vue.js frontend - Multi-device support (ADB, HDC, iOS) - Real-time WebSocket updates for task progress - Device management with status tracking - Task queue with execution controls (start/stop/re-execute) - Detailed task information display (thinking, actions, completion messages) - Screenshot viewing per device - LAN deployment support with configurable CORS - Callback Hooks: Interrupt and modify task execution - step_callback: Called after each step with StepResult - before_action_callback: Called before executing action - Support for task interruption and dynamic task switching - Example scripts demonstrating callback usage - Configuration: Environment-based configuration - .env file support for all settings - .env.example template with documentation - Model API configuration (base URL, model name, API key) - Dashboard configuration (host, port, CORS, device type) - Phone agent configuration (delays, max steps, language) Technical improvements: - Fixed forward reference issue with StepResult - Added package exports for callback types and configs - Enhanced dependencies with FastAPI, WebSocket support - Thread-safe task execution with device locking - Async WebSocket broadcasting from sync thread pool Co-Authored-By: Claude <noreply@anthropic.com>
194 lines
5.0 KiB
Python
194 lines
5.0 KiB
Python
"""
|
|
Device management API endpoints.
|
|
"""
|
|
|
|
from typing import List
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
from dashboard.dependencies import get_device_manager, get_ws_manager
|
|
from dashboard.models.device import DeviceSchema, DeviceStatus
|
|
from dashboard.services.device_manager import DeviceManager
|
|
from dashboard.services.websocket_manager import WebSocketManager
|
|
|
|
router = APIRouter(prefix="/devices", tags=["devices"])
|
|
|
|
|
|
@router.get("", response_model=List[DeviceSchema])
|
|
async def list_devices(
|
|
device_manager: DeviceManager = Depends(get_device_manager),
|
|
):
|
|
"""List all connected devices.
|
|
|
|
Returns:
|
|
List of device schemas
|
|
"""
|
|
devices = await device_manager.refresh_devices()
|
|
return [
|
|
DeviceSchema(
|
|
device_id=d.device_id,
|
|
status=d.status,
|
|
device_type=d.device_type,
|
|
model=d.model,
|
|
android_version=d.android_version,
|
|
current_app=d.current_app,
|
|
last_seen=d.last_seen,
|
|
is_connected=d.is_connected,
|
|
)
|
|
for d in devices
|
|
]
|
|
|
|
|
|
@router.get("/refresh")
|
|
async def refresh_devices(
|
|
device_manager: DeviceManager = Depends(get_device_manager),
|
|
ws_manager: WebSocketManager = Depends(get_ws_manager),
|
|
):
|
|
"""Rescan for connected devices.
|
|
|
|
Returns:
|
|
Refresh confirmation
|
|
"""
|
|
devices = await device_manager.refresh_devices()
|
|
|
|
# Broadcast device update
|
|
for device in devices:
|
|
await ws_manager.broadcast_device_update(
|
|
device.device_id,
|
|
{
|
|
"status": device.status,
|
|
"is_connected": device.is_connected,
|
|
"model": device.model,
|
|
"current_app": device.current_app,
|
|
"last_seen": device.last_seen.isoformat(),
|
|
},
|
|
)
|
|
|
|
return {"message": "Devices refreshed", "count": len(devices)}
|
|
|
|
|
|
@router.get("/{device_id}", response_model=DeviceSchema)
|
|
async def get_device(
|
|
device_id: str,
|
|
device_manager: DeviceManager = Depends(get_device_manager),
|
|
):
|
|
"""Get device details.
|
|
|
|
Args:
|
|
device_id: Device identifier
|
|
|
|
Returns:
|
|
Device schema
|
|
|
|
Raises:
|
|
HTTPException: If device not found
|
|
"""
|
|
device = await device_manager.get_device(device_id)
|
|
|
|
if device is None:
|
|
raise HTTPException(status_code=404, detail=f"Device {device_id} not found")
|
|
|
|
return DeviceSchema(
|
|
device_id=device.device_id,
|
|
status=device.status,
|
|
device_type=device.device_type,
|
|
model=device.model,
|
|
android_version=device.android_version,
|
|
current_app=device.current_app,
|
|
last_seen=device.last_seen,
|
|
is_connected=device.is_connected,
|
|
)
|
|
|
|
|
|
@router.post("/{device_id}/connect")
|
|
async def connect_device(
|
|
device_id: str,
|
|
address: str | None = None,
|
|
device_manager: DeviceManager = Depends(get_device_manager),
|
|
):
|
|
"""Connect to device via WiFi.
|
|
|
|
Args:
|
|
device_id: Device identifier (for route matching)
|
|
address: Device address (IP:PORT)
|
|
|
|
Returns:
|
|
Connection result
|
|
"""
|
|
if not address:
|
|
raise HTTPException(status_code=400, detail="Address is required")
|
|
|
|
success = await device_manager.connect_device(address)
|
|
|
|
if success:
|
|
return {"message": f"Connected to {address}"}
|
|
else:
|
|
raise HTTPException(status_code=500, detail=f"Failed to connect to {address}")
|
|
|
|
|
|
@router.post("/{device_id}/disconnect")
|
|
async def disconnect_device(
|
|
device_id: str,
|
|
address: str | None = None,
|
|
device_manager: DeviceManager = Depends(get_device_manager),
|
|
):
|
|
"""Disconnect from device.
|
|
|
|
Args:
|
|
device_id: Device identifier (for route matching)
|
|
address: Device address (IP:PORT)
|
|
|
|
Returns:
|
|
Disconnection result
|
|
"""
|
|
if not address:
|
|
raise HTTPException(status_code=400, detail="Address is required")
|
|
|
|
success = await device_manager.disconnect_device(address)
|
|
|
|
if success:
|
|
return {"message": f"Disconnected from {address}"}
|
|
else:
|
|
raise HTTPException(
|
|
status_code=500, detail=f"Failed to disconnect from {address}"
|
|
)
|
|
|
|
|
|
@router.get("/{device_id}/screenshot")
|
|
async def get_device_screenshot(
|
|
device_id: str,
|
|
device_manager: DeviceManager = Depends(get_device_manager),
|
|
):
|
|
"""Get current device screenshot.
|
|
|
|
Args:
|
|
device_id: Device identifier
|
|
|
|
Returns:
|
|
Screenshot data (base64 encoded)
|
|
"""
|
|
screenshot = await device_manager.get_screenshot(device_id)
|
|
|
|
if screenshot is None:
|
|
raise HTTPException(status_code=500, detail="Failed to capture screenshot")
|
|
|
|
return {"device_id": device_id, "screenshot": screenshot}
|
|
|
|
|
|
@router.get("/{device_id}/current-app")
|
|
async def get_current_app(
|
|
device_id: str,
|
|
device_manager: DeviceManager = Depends(get_device_manager),
|
|
):
|
|
"""Get current app for device.
|
|
|
|
Args:
|
|
device_id: Device identifier
|
|
|
|
Returns:
|
|
Current app package name
|
|
"""
|
|
app = await device_manager.get_current_app(device_id)
|
|
|
|
return {"device_id": device_id, "current_app": app}
|