Some checks failed
Validate / validate (push) Failing after 8s
- Generate default templates (MSS, DXcam, WGC) in memory from EngineRegistry at startup - Only persist user-created templates to JSON, skip defaults on load/save - Add capture_template_id to Device model and DeviceCreate schema - Remember last used template in localStorage, use it for new devices with fallback - Split Device Settings dialog into General Settings and Capture Settings - Add capture settings button (🎬) to device card - Separate default and custom templates with visual separator in Templates tab - Add capture engine integration to ProcessorManager - Add CLAUDE.md with git commit/push policy and server restart instructions - Add en/ru localization for all new UI elements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
173 lines
5.5 KiB
Python
173 lines
5.5 KiB
Python
"""FastAPI application entry point."""
|
|
|
|
import sys
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse, FileResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from wled_controller import __version__
|
|
from wled_controller.api import router
|
|
from wled_controller.api.routes import init_dependencies
|
|
from wled_controller.config import get_config
|
|
from wled_controller.core.processor_manager import ProcessorManager
|
|
from wled_controller.storage import DeviceStore
|
|
from wled_controller.storage.template_store import TemplateStore
|
|
from wled_controller.utils import setup_logging, get_logger
|
|
|
|
# Initialize logging
|
|
setup_logging()
|
|
logger = get_logger(__name__)
|
|
|
|
# Get configuration
|
|
config = get_config()
|
|
|
|
# Initialize storage and processing
|
|
device_store = DeviceStore(config.storage.devices_file)
|
|
template_store = TemplateStore(config.storage.templates_file)
|
|
processor_manager = ProcessorManager()
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Application lifespan manager.
|
|
|
|
Handles startup and shutdown events.
|
|
"""
|
|
# Startup
|
|
logger.info(f"Starting WLED Screen Controller v{__version__}")
|
|
logger.info(f"Python version: {sys.version}")
|
|
logger.info(f"Server listening on {config.server.host}:{config.server.port}")
|
|
|
|
# Validate authentication configuration
|
|
if not config.auth.api_keys:
|
|
logger.error("=" * 70)
|
|
logger.error("CRITICAL: No API keys configured!")
|
|
logger.error("Authentication is REQUIRED for all API requests.")
|
|
logger.error("Please add API keys to your configuration:")
|
|
logger.error(" 1. Generate keys: openssl rand -hex 32")
|
|
logger.error(" 2. Add to config/default_config.yaml under auth.api_keys")
|
|
logger.error(" 3. Format: label: \"your-generated-key\"")
|
|
logger.error("=" * 70)
|
|
raise RuntimeError("No API keys configured - server cannot start without authentication")
|
|
|
|
# Log authentication status
|
|
logger.info(f"API Authentication: ENFORCED ({len(config.auth.api_keys)} clients configured)")
|
|
client_labels = ", ".join(config.auth.api_keys.keys())
|
|
logger.info(f"Authorized clients: {client_labels}")
|
|
logger.info("All API requests require valid Bearer token authentication")
|
|
|
|
# Initialize API dependencies
|
|
init_dependencies(device_store, template_store, processor_manager)
|
|
|
|
# Load existing devices into processor manager
|
|
devices = device_store.get_all_devices()
|
|
for device in devices:
|
|
try:
|
|
processor_manager.add_device(
|
|
device_id=device.id,
|
|
device_url=device.url,
|
|
led_count=device.led_count,
|
|
settings=device.settings,
|
|
calibration=device.calibration,
|
|
capture_template_id=device.capture_template_id,
|
|
)
|
|
logger.info(f"Loaded device: {device.name} ({device.id})")
|
|
except Exception as e:
|
|
logger.error(f"Failed to load device {device.id}: {e}")
|
|
|
|
logger.info(f"Loaded {len(devices)} devices from storage")
|
|
|
|
# Start background health monitoring for all devices
|
|
await processor_manager.start_health_monitoring()
|
|
|
|
yield
|
|
|
|
# Shutdown
|
|
logger.info("Shutting down WLED Screen Controller")
|
|
|
|
# Stop all processing
|
|
try:
|
|
await processor_manager.stop_all()
|
|
logger.info("Stopped all processors")
|
|
except Exception as e:
|
|
logger.error(f"Error stopping processors: {e}")
|
|
|
|
# Create FastAPI application
|
|
app = FastAPI(
|
|
title="WLED Screen Controller",
|
|
description="Control WLED devices based on screen content for ambient lighting",
|
|
version=__version__,
|
|
lifespan=lifespan,
|
|
docs_url="/docs",
|
|
redoc_url="/redoc",
|
|
openapi_url="/openapi.json",
|
|
)
|
|
|
|
# Configure CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=config.server.cors_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Include API routes
|
|
app.include_router(router)
|
|
|
|
# Mount static files
|
|
static_path = Path(__file__).parent / "static"
|
|
if static_path.exists():
|
|
app.mount("/static", StaticFiles(directory=str(static_path)), name="static")
|
|
logger.info(f"Mounted static files from {static_path}")
|
|
else:
|
|
logger.warning(f"Static files directory not found: {static_path}")
|
|
|
|
|
|
@app.exception_handler(Exception)
|
|
async def global_exception_handler(request, exc):
|
|
"""Global exception handler for unhandled errors."""
|
|
logger.error(f"Unhandled exception: {exc}", exc_info=True)
|
|
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"error": "InternalServerError",
|
|
"message": "An unexpected error occurred",
|
|
"detail": str(exc) if config.server.log_level == "DEBUG" else None,
|
|
},
|
|
)
|
|
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
"""Serve the web UI dashboard."""
|
|
static_path = Path(__file__).parent / "static" / "index.html"
|
|
if static_path.exists():
|
|
return FileResponse(static_path)
|
|
|
|
# Fallback to JSON if static files not found
|
|
return {
|
|
"name": "WLED Screen Controller",
|
|
"version": __version__,
|
|
"docs": "/docs",
|
|
"health": "/health",
|
|
"api": "/api/v1",
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
uvicorn.run(
|
|
"wled_controller.main:app",
|
|
host=config.server.host,
|
|
port=config.server.port,
|
|
log_level=config.server.log_level.lower(),
|
|
reload=False, # Disabled due to watchfiles infinite reload loop
|
|
)
|