diff --git a/server/src/wled_controller/api/routes/picture_targets.py b/server/src/wled_controller/api/routes/picture_targets.py index c586e1c..aefebc1 100644 --- a/server/src/wled_controller/api/routes/picture_targets.py +++ b/server/src/wled_controller/api/routes/picture_targets.py @@ -101,6 +101,7 @@ def _target_to_response(target) -> PictureTargetResponse: keepalive_interval=target.keepalive_interval, state_check_interval=target.state_check_interval, description=target.description, + auto_start=target.auto_start, created_at=target.created_at, updated_at=target.updated_at, ) @@ -112,6 +113,7 @@ def _target_to_response(target) -> PictureTargetResponse: picture_source_id=target.picture_source_id, key_colors_settings=_kc_settings_to_schema(target.settings), description=target.description, + auto_start=target.auto_start, created_at=target.created_at, updated_at=target.updated_at, ) @@ -121,6 +123,7 @@ def _target_to_response(target) -> PictureTargetResponse: name=target.name, target_type=target.target_type, description=target.description, + auto_start=target.auto_start, created_at=target.created_at, updated_at=target.updated_at, ) @@ -159,6 +162,7 @@ async def create_target( picture_source_id=data.picture_source_id, key_colors_settings=kc_settings, description=data.description, + auto_start=data.auto_start, ) # Register in processor manager @@ -274,6 +278,7 @@ async def update_target( state_check_interval=data.state_check_interval, key_colors_settings=kc_settings, description=data.description, + auto_start=data.auto_start, ) # Detect KC brightness VS change (inside key_colors_settings) diff --git a/server/src/wled_controller/api/schemas/picture_targets.py b/server/src/wled_controller/api/schemas/picture_targets.py index 5605b51..758013b 100644 --- a/server/src/wled_controller/api/schemas/picture_targets.py +++ b/server/src/wled_controller/api/schemas/picture_targets.py @@ -62,6 +62,7 @@ class PictureTargetCreate(BaseModel): picture_source_id: str = Field(default="", description="Picture source ID (for key_colors targets)") key_colors_settings: Optional[KeyColorsSettingsSchema] = Field(None, description="Key colors settings (for key_colors targets)") description: Optional[str] = Field(None, description="Optional description", max_length=500) + auto_start: bool = Field(default=False, description="Auto-start on server boot") class PictureTargetUpdate(BaseModel): @@ -79,6 +80,7 @@ class PictureTargetUpdate(BaseModel): picture_source_id: Optional[str] = Field(None, description="Picture source ID (for key_colors targets)") key_colors_settings: Optional[KeyColorsSettingsSchema] = Field(None, description="Key colors settings (for key_colors targets)") description: Optional[str] = Field(None, description="Optional description", max_length=500) + auto_start: Optional[bool] = Field(None, description="Auto-start on server boot") class PictureTargetResponse(BaseModel): @@ -98,6 +100,7 @@ class PictureTargetResponse(BaseModel): picture_source_id: str = Field(default="", description="Picture source ID (key_colors)") key_colors_settings: Optional[KeyColorsSettingsSchema] = Field(None, description="Key colors settings") description: Optional[str] = Field(None, description="Description") + auto_start: bool = Field(default=False, description="Auto-start on server boot") created_at: datetime = Field(description="Creation timestamp") updated_at: datetime = Field(description="Last update timestamp") diff --git a/server/src/wled_controller/main.py b/server/src/wled_controller/main.py index db5082f..1157a63 100644 --- a/server/src/wled_controller/main.py +++ b/server/src/wled_controller/main.py @@ -147,6 +147,19 @@ async def lifespan(app: FastAPI): # Start profile engine (evaluates conditions and auto-starts/stops targets) await profile_engine.start() + # Auto-start targets with auto_start=True + auto_started = 0 + for target in targets: + if getattr(target, "auto_start", False): + try: + await processor_manager.start_processing(target.id) + auto_started += 1 + logger.info(f"Auto-started target: {target.name} ({target.id})") + except Exception as e: + logger.warning(f"Failed to auto-start target {target.id}: {e}") + if auto_started: + logger.info(f"Auto-started {auto_started} target(s)") + yield # Shutdown diff --git a/server/src/wled_controller/static/css/cards.css b/server/src/wled_controller/static/css/cards.css index 8fccee4..415e591 100644 --- a/server/src/wled_controller/static/css/cards.css +++ b/server/src/wled_controller/static/css/cards.css @@ -103,6 +103,30 @@ section { position: static; } +.card-autostart-btn { + background: none; + border: none; + color: #777; + font-size: 1rem; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 4px; + transition: color 0.2s, background 0.2s; +} + +.card-autostart-btn:hover { + color: var(--warning-color, #ffc107); + background: rgba(255, 193, 7, 0.1); +} + +.card-autostart-btn.active { + color: var(--warning-color, #ffc107); +} + .card-power-btn { background: none; border: none; diff --git a/server/src/wled_controller/static/js/app.js b/server/src/wled_controller/static/js/app.js index 31e9b1a..267a466 100644 --- a/server/src/wled_controller/static/js/app.js +++ b/server/src/wled_controller/static/js/app.js @@ -88,7 +88,7 @@ import { showTargetEditor, closeTargetEditorModal, forceCloseTargetEditorModal, saveTargetEditor, startTargetProcessing, stopTargetProcessing, startTargetOverlay, stopTargetOverlay, deleteTarget, - cloneTarget, toggleLedPreview, + cloneTarget, toggleLedPreview, toggleTargetAutoStart, expandAllTargetSections, collapseAllTargetSections, } from './features/targets.js'; @@ -302,6 +302,7 @@ Object.assign(window, { deleteTarget, cloneTarget, toggleLedPreview, + toggleTargetAutoStart, // color-strip sources showCSSEditor, diff --git a/server/src/wled_controller/static/js/features/dashboard.js b/server/src/wled_controller/static/js/features/dashboard.js index a3ae4c8..f1ec917 100644 --- a/server/src/wled_controller/static/js/features/dashboard.js +++ b/server/src/wled_controller/static/js/features/dashboard.js @@ -363,6 +363,38 @@ export async function loadDashboard(forceFullRender = false) { return; } + const autoStartTargets = enriched.filter(t => t.auto_start); + if (autoStartTargets.length > 0) { + const autoStartItems = autoStartTargets.map(target => { + const isRunning = !!(target.state && target.state.processing); + const device = devicesMap[target.device_id]; + const deviceName = device ? device.name : ''; + const typeIcon = target.target_type === 'key_colors' ? '🎨' : '💡'; + const statusBadge = isRunning + ? `${t('profiles.status.active')}` + : `${t('profiles.status.inactive')}`; + return `