Add Dangerous Pi MVP implementation - complete backend and system integration
This commit adds the complete Dangerous Pi web management interface with all MVP features implemented and tested locally. ## New Features ### Backend (Python + FastAPI) - Complete FastAPI backend with async support - 40+ API endpoints (Health, PM3, WiFi, Updates, UPS, BLE, Plugins) - 6 managers: Session, WiFi, Update, UPS, BLE, Plugin - SQLite database with sessions, config, history, crash reports - Server-Sent Events (SSE) for real-time notifications - Mock PM3 worker for development without hardware ### WiFi Manager - Interface detection (USB vs built-in) - Network scanning with signal strength - Mode switching (AP/Client/Dual/Auto/Off) - Network connection with password support - Hidden SSID and saved networks support - Static IP and DHCP configuration - 10 WiFi API endpoints ### Update Manager - GitHub releases API integration - Automatic periodic update checks - Semantic version comparison - Update download with progress tracking - SHA256 checksum verification - Automatic installation with backup and rollback - PM3 client rebuild after updates - 6 Update API endpoints ### UPS Manager - I2C battery monitoring (MAX17040-compatible) - Battery percentage, voltage, current tracking - Power source detection (AC/Battery) - Safe shutdown triggers at configurable thresholds - Event callbacks for battery warnings - SSE and BLE notification integration - 3 UPS API endpoints ### BLE Manager - Bluetooth Low Energy notification support - Auto-detects BLE capability - Multiple notification types (updates, battery, shutdown, etc.) - BLE advertising management - Device connection tracking - 4 BLE API endpoints ### Plugin Framework - Dynamic plugin loading/unloading - Plugin lifecycle management (load, enable, disable, unload) - Hook system for extensibility - JSON-based metadata - Example "Hello World" plugin included - 7 Plugin API endpoints ### Frontend (Remix.js + React) - Cyberpunk-themed responsive UI - Dashboard with system status - PM3 command interface with history - Settings page with WiFi and Update management - Command logs viewer - Theme toggle (Dark/Light/Auto) - Server-side rendering (SSR) - Mobile-first responsive design ### System Integration - Systemd service with security hardening - Automated install/uninstall scripts - Environment configuration template - Hardware access groups (i2c, bluetooth, gpio, dialout) - Pi-gen stage 04 integration for OS image building - Port conflict resolution with ttyd-bash - I2C interface auto-enable for UPS HAT ### Testing - test_backend.py - Backend API tests - test_ups.py - UPS manager tests - test_ble.py - BLE manager tests - test_plugins.py - Plugin manager tests - All tests passing locally ### Documentation - 12 comprehensive documentation files - claude.md - AI development guide - WIFI_MANAGER.md - WiFi management guide - UPDATE_MANAGER.md - Update system guide - PORT_CONFLICT.md - Port conflict resolution guide - MVP_COMPLETE.md - MVP implementation summary - PROJECT_STATUS.md - Project status and roadmap - systemd/README.md - Service management docs - pi-gen integration documentation ## Technical Details - ~5,000+ lines of backend code - 11 Python dependencies (smbus2 added for UPS) - FastAPI with async/await throughout - Type hints and docstrings on all functions - RESTful API design with SSE for notifications - Security hardening (non-root, protected dirs, resource limits) ## Next Steps - Deploy to Raspberry Pi Zero 2 W hardware - Test with real Proxmark3 device - Test UPS HAT integration - Test BLE on Pi hardware - Build custom OS image with pi-gen - Performance optimization for Pi Zero 2 W 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
241
app/backend/api/plugins.py
Normal file
241
app/backend/api/plugins.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""Plugin management API endpoints."""
|
||||
from typing import List, Dict, Any, Optional
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..managers.plugin_manager import get_plugin_manager
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class PluginMetadataResponse(BaseModel):
|
||||
"""Plugin metadata response model."""
|
||||
id: str
|
||||
name: str
|
||||
version: str
|
||||
description: str
|
||||
author: str
|
||||
homepage: Optional[str] = None
|
||||
dependencies: List[str] = []
|
||||
permissions: List[str] = []
|
||||
|
||||
|
||||
class PluginInfoResponse(BaseModel):
|
||||
"""Plugin info response model."""
|
||||
metadata: PluginMetadataResponse
|
||||
status: str
|
||||
enabled: bool
|
||||
error_message: Optional[str] = None
|
||||
|
||||
|
||||
class PluginActionRequest(BaseModel):
|
||||
"""Request model for plugin actions."""
|
||||
plugin_id: str
|
||||
|
||||
|
||||
@router.get("/discover")
|
||||
async def discover_plugins():
|
||||
"""Discover all available plugins.
|
||||
|
||||
Returns:
|
||||
List of discovered plugin IDs
|
||||
"""
|
||||
try:
|
||||
manager = get_plugin_manager()
|
||||
discovered = await manager.discover_plugins()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"plugins": discovered,
|
||||
"count": len(discovered)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/list", response_model=List[PluginInfoResponse])
|
||||
async def list_plugins():
|
||||
"""Get list of all plugins.
|
||||
|
||||
Returns:
|
||||
List of plugin information
|
||||
"""
|
||||
try:
|
||||
manager = get_plugin_manager()
|
||||
plugins = manager.get_all_plugins()
|
||||
|
||||
result = []
|
||||
for plugin_id, plugin_info in plugins.items():
|
||||
result.append(PluginInfoResponse(
|
||||
metadata=PluginMetadataResponse(
|
||||
id=plugin_info.metadata.id,
|
||||
name=plugin_info.metadata.name,
|
||||
version=plugin_info.metadata.version,
|
||||
description=plugin_info.metadata.description,
|
||||
author=plugin_info.metadata.author,
|
||||
homepage=plugin_info.metadata.homepage,
|
||||
dependencies=plugin_info.metadata.dependencies,
|
||||
permissions=plugin_info.metadata.permissions
|
||||
),
|
||||
status=plugin_info.status.value,
|
||||
enabled=plugin_info.enabled,
|
||||
error_message=plugin_info.error_message
|
||||
))
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/{plugin_id}", response_model=PluginInfoResponse)
|
||||
async def get_plugin_info(plugin_id: str):
|
||||
"""Get information about a specific plugin.
|
||||
|
||||
Args:
|
||||
plugin_id: ID of the plugin
|
||||
|
||||
Returns:
|
||||
Plugin information
|
||||
"""
|
||||
try:
|
||||
manager = get_plugin_manager()
|
||||
plugin_info = manager.get_plugin_info(plugin_id)
|
||||
|
||||
if not plugin_info:
|
||||
raise HTTPException(status_code=404, detail="Plugin not found")
|
||||
|
||||
return PluginInfoResponse(
|
||||
metadata=PluginMetadataResponse(
|
||||
id=plugin_info.metadata.id,
|
||||
name=plugin_info.metadata.name,
|
||||
version=plugin_info.metadata.version,
|
||||
description=plugin_info.metadata.description,
|
||||
author=plugin_info.metadata.author,
|
||||
homepage=plugin_info.metadata.homepage,
|
||||
dependencies=plugin_info.metadata.dependencies,
|
||||
permissions=plugin_info.metadata.permissions
|
||||
),
|
||||
status=plugin_info.status.value,
|
||||
enabled=plugin_info.enabled,
|
||||
error_message=plugin_info.error_message
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/enable")
|
||||
async def enable_plugin(request: PluginActionRequest):
|
||||
"""Enable a plugin.
|
||||
|
||||
Args:
|
||||
request: Plugin action request with plugin_id
|
||||
|
||||
Returns:
|
||||
Success message
|
||||
"""
|
||||
try:
|
||||
manager = get_plugin_manager()
|
||||
success = await manager.enable_plugin(request.plugin_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to enable plugin")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Plugin {request.plugin_id} enabled"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/disable")
|
||||
async def disable_plugin(request: PluginActionRequest):
|
||||
"""Disable a plugin.
|
||||
|
||||
Args:
|
||||
request: Plugin action request with plugin_id
|
||||
|
||||
Returns:
|
||||
Success message
|
||||
"""
|
||||
try:
|
||||
manager = get_plugin_manager()
|
||||
success = await manager.disable_plugin(request.plugin_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to disable plugin")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Plugin {request.plugin_id} disabled"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/load")
|
||||
async def load_plugin(request: PluginActionRequest):
|
||||
"""Load a plugin into memory.
|
||||
|
||||
Args:
|
||||
request: Plugin action request with plugin_id
|
||||
|
||||
Returns:
|
||||
Success message
|
||||
"""
|
||||
try:
|
||||
manager = get_plugin_manager()
|
||||
success = await manager.load_plugin(request.plugin_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to load plugin")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Plugin {request.plugin_id} loaded"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/unload")
|
||||
async def unload_plugin(request: PluginActionRequest):
|
||||
"""Unload a plugin from memory.
|
||||
|
||||
Args:
|
||||
request: Plugin action request with plugin_id
|
||||
|
||||
Returns:
|
||||
Success message
|
||||
"""
|
||||
try:
|
||||
manager = get_plugin_manager()
|
||||
success = await manager.unload_plugin(request.plugin_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to unload plugin")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Plugin {request.plugin_id} unloaded"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Reference in New Issue
Block a user