Rename profiles to automations across backend and frontend

Rename the "profiles" entity to "automations" throughout the entire
codebase for clarity. Updates Python models, storage, API routes/schemas,
engine, frontend JS modules, HTML templates, CSS classes, i18n keys
(en/ru/zh), dashboard, tutorials, and command palette.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 18:01:39 +03:00
parent da3e53e1f1
commit 21248e2dc9
39 changed files with 1180 additions and 1179 deletions

View File

@@ -14,7 +14,7 @@ from .routes.audio import router as audio_router
from .routes.audio_sources import router as audio_sources_router
from .routes.audio_templates import router as audio_templates_router
from .routes.value_sources import router as value_sources_router
from .routes.profiles import router as profiles_router
from .routes.automations import router as automations_router
from .routes.scene_presets import router as scene_presets_router
router = APIRouter()
@@ -30,7 +30,7 @@ router.include_router(audio_sources_router)
router.include_router(audio_templates_router)
router.include_router(value_sources_router)
router.include_router(picture_targets_router)
router.include_router(profiles_router)
router.include_router(automations_router)
router.include_router(scene_presets_router)
__all__ = ["router"]

View File

@@ -11,9 +11,9 @@ from wled_controller.storage.color_strip_store import ColorStripStore
from wled_controller.storage.audio_source_store import AudioSourceStore
from wled_controller.storage.audio_template_store import AudioTemplateStore
from wled_controller.storage.value_source_store import ValueSourceStore
from wled_controller.storage.profile_store import ProfileStore
from wled_controller.storage.automation_store import AutomationStore
from wled_controller.storage.scene_preset_store import ScenePresetStore
from wled_controller.core.profiles.profile_engine import ProfileEngine
from wled_controller.core.automations.automation_engine import AutomationEngine
from wled_controller.core.backup.auto_backup import AutoBackupEngine
# Global instances (initialized in main.py)
@@ -29,9 +29,9 @@ _audio_source_store: AudioSourceStore | None = None
_audio_template_store: AudioTemplateStore | None = None
_value_source_store: ValueSourceStore | None = None
_processor_manager: ProcessorManager | None = None
_profile_store: ProfileStore | None = None
_automation_store: AutomationStore | None = None
_scene_preset_store: ScenePresetStore | None = None
_profile_engine: ProfileEngine | None = None
_automation_engine: AutomationEngine | None = None
def get_device_store() -> DeviceStore:
@@ -111,11 +111,11 @@ def get_processor_manager() -> ProcessorManager:
return _processor_manager
def get_profile_store() -> ProfileStore:
"""Get profile store dependency."""
if _profile_store is None:
raise RuntimeError("Profile store not initialized")
return _profile_store
def get_automation_store() -> AutomationStore:
"""Get automation store dependency."""
if _automation_store is None:
raise RuntimeError("Automation store not initialized")
return _automation_store
def get_scene_preset_store() -> ScenePresetStore:
@@ -125,11 +125,11 @@ def get_scene_preset_store() -> ScenePresetStore:
return _scene_preset_store
def get_profile_engine() -> ProfileEngine:
"""Get profile engine dependency."""
if _profile_engine is None:
raise RuntimeError("Profile engine not initialized")
return _profile_engine
def get_automation_engine() -> AutomationEngine:
"""Get automation engine dependency."""
if _automation_engine is None:
raise RuntimeError("Automation engine not initialized")
return _automation_engine
def get_auto_backup_engine() -> AutoBackupEngine:
@@ -151,16 +151,16 @@ def init_dependencies(
audio_source_store: AudioSourceStore | None = None,
audio_template_store: AudioTemplateStore | None = None,
value_source_store: ValueSourceStore | None = None,
profile_store: ProfileStore | None = None,
automation_store: AutomationStore | None = None,
scene_preset_store: ScenePresetStore | None = None,
profile_engine: ProfileEngine | None = None,
automation_engine: AutomationEngine | None = None,
auto_backup_engine: AutoBackupEngine | None = None,
):
"""Initialize global dependencies."""
global _device_store, _template_store, _processor_manager
global _pp_template_store, _pattern_template_store, _picture_source_store, _picture_target_store
global _color_strip_store, _audio_source_store, _audio_template_store
global _value_source_store, _profile_store, _scene_preset_store, _profile_engine, _auto_backup_engine
global _value_source_store, _automation_store, _scene_preset_store, _automation_engine, _auto_backup_engine
_device_store = device_store
_template_store = template_store
_processor_manager = processor_manager
@@ -172,7 +172,7 @@ def init_dependencies(
_audio_source_store = audio_source_store
_audio_template_store = audio_template_store
_value_source_store = value_source_store
_profile_store = profile_store
_automation_store = automation_store
_scene_preset_store = scene_preset_store
_profile_engine = profile_engine
_automation_engine = automation_engine
_auto_backup_engine = auto_backup_engine

View File

@@ -1,22 +1,22 @@
"""Profile management API routes."""
"""Automation management API routes."""
from fastapi import APIRouter, Depends, HTTPException
from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
get_profile_engine,
get_profile_store,
get_automation_engine,
get_automation_store,
get_scene_preset_store,
)
from wled_controller.api.schemas.profiles import (
from wled_controller.api.schemas.automations import (
AutomationCreate,
AutomationListResponse,
AutomationResponse,
AutomationUpdate,
ConditionSchema,
ProfileCreate,
ProfileListResponse,
ProfileResponse,
ProfileUpdate,
)
from wled_controller.core.profiles.profile_engine import ProfileEngine
from wled_controller.storage.profile import (
from wled_controller.core.automations.automation_engine import AutomationEngine
from wled_controller.storage.automation import (
AlwaysCondition,
ApplicationCondition,
Condition,
@@ -25,7 +25,7 @@ from wled_controller.storage.profile import (
SystemIdleCondition,
TimeOfDayCondition,
)
from wled_controller.storage.profile_store import ProfileStore
from wled_controller.storage.automation_store import AutomationStore
from wled_controller.storage.scene_preset_store import ScenePresetStore
from wled_controller.utils import get_logger
@@ -71,22 +71,22 @@ def _condition_to_schema(c: Condition) -> ConditionSchema:
return ConditionSchema(**d)
def _profile_to_response(profile, engine: ProfileEngine) -> ProfileResponse:
state = engine.get_profile_state(profile.id)
return ProfileResponse(
id=profile.id,
name=profile.name,
enabled=profile.enabled,
condition_logic=profile.condition_logic,
conditions=[_condition_to_schema(c) for c in profile.conditions],
scene_preset_id=profile.scene_preset_id,
deactivation_mode=profile.deactivation_mode,
deactivation_scene_preset_id=profile.deactivation_scene_preset_id,
def _automation_to_response(automation, engine: AutomationEngine) -> AutomationResponse:
state = engine.get_automation_state(automation.id)
return AutomationResponse(
id=automation.id,
name=automation.name,
enabled=automation.enabled,
condition_logic=automation.condition_logic,
conditions=[_condition_to_schema(c) for c in automation.conditions],
scene_preset_id=automation.scene_preset_id,
deactivation_mode=automation.deactivation_mode,
deactivation_scene_preset_id=automation.deactivation_scene_preset_id,
is_active=state["is_active"],
last_activated_at=state.get("last_activated_at"),
last_deactivated_at=state.get("last_deactivated_at"),
created_at=profile.created_at,
updated_at=profile.updated_at,
created_at=automation.created_at,
updated_at=automation.updated_at,
)
@@ -115,19 +115,19 @@ def _validate_scene_refs(
# ===== CRUD Endpoints =====
@router.post(
"/api/v1/profiles",
response_model=ProfileResponse,
tags=["Profiles"],
"/api/v1/automations",
response_model=AutomationResponse,
tags=["Automations"],
status_code=201,
)
async def create_profile(
data: ProfileCreate,
async def create_automation(
data: AutomationCreate,
_auth: AuthRequired,
store: ProfileStore = Depends(get_profile_store),
engine: ProfileEngine = Depends(get_profile_engine),
store: AutomationStore = Depends(get_automation_store),
engine: AutomationEngine = Depends(get_automation_engine),
scene_store: ScenePresetStore = Depends(get_scene_preset_store),
):
"""Create a new profile."""
"""Create a new automation."""
_validate_condition_logic(data.condition_logic)
_validate_scene_refs(data.scene_preset_id, data.deactivation_scene_preset_id, scene_store)
@@ -136,7 +136,7 @@ async def create_profile(
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
profile = store.create_profile(
automation = store.create_automation(
name=data.name,
enabled=data.enabled,
condition_logic=data.condition_logic,
@@ -146,64 +146,64 @@ async def create_profile(
deactivation_scene_preset_id=data.deactivation_scene_preset_id,
)
if profile.enabled:
if automation.enabled:
await engine.trigger_evaluate()
return _profile_to_response(profile, engine)
return _automation_to_response(automation, engine)
@router.get(
"/api/v1/profiles",
response_model=ProfileListResponse,
tags=["Profiles"],
"/api/v1/automations",
response_model=AutomationListResponse,
tags=["Automations"],
)
async def list_profiles(
async def list_automations(
_auth: AuthRequired,
store: ProfileStore = Depends(get_profile_store),
engine: ProfileEngine = Depends(get_profile_engine),
store: AutomationStore = Depends(get_automation_store),
engine: AutomationEngine = Depends(get_automation_engine),
):
"""List all profiles."""
profiles = store.get_all_profiles()
return ProfileListResponse(
profiles=[_profile_to_response(p, engine) for p in profiles],
count=len(profiles),
"""List all automations."""
automations = store.get_all_automations()
return AutomationListResponse(
automations=[_automation_to_response(a, engine) for a in automations],
count=len(automations),
)
@router.get(
"/api/v1/profiles/{profile_id}",
response_model=ProfileResponse,
tags=["Profiles"],
"/api/v1/automations/{automation_id}",
response_model=AutomationResponse,
tags=["Automations"],
)
async def get_profile(
profile_id: str,
async def get_automation(
automation_id: str,
_auth: AuthRequired,
store: ProfileStore = Depends(get_profile_store),
engine: ProfileEngine = Depends(get_profile_engine),
store: AutomationStore = Depends(get_automation_store),
engine: AutomationEngine = Depends(get_automation_engine),
):
"""Get a single profile."""
"""Get a single automation."""
try:
profile = store.get_profile(profile_id)
automation = store.get_automation(automation_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
return _profile_to_response(profile, engine)
return _automation_to_response(automation, engine)
@router.put(
"/api/v1/profiles/{profile_id}",
response_model=ProfileResponse,
tags=["Profiles"],
"/api/v1/automations/{automation_id}",
response_model=AutomationResponse,
tags=["Automations"],
)
async def update_profile(
profile_id: str,
data: ProfileUpdate,
async def update_automation(
automation_id: str,
data: AutomationUpdate,
_auth: AuthRequired,
store: ProfileStore = Depends(get_profile_store),
engine: ProfileEngine = Depends(get_profile_engine),
store: AutomationStore = Depends(get_automation_store),
engine: AutomationEngine = Depends(get_automation_engine),
scene_store: ScenePresetStore = Depends(get_scene_preset_store),
):
"""Update a profile."""
"""Update an automation."""
if data.condition_logic is not None:
_validate_condition_logic(data.condition_logic)
@@ -220,11 +220,11 @@ async def update_profile(
try:
# If disabling, deactivate first
if data.enabled is False:
await engine.deactivate_if_active(profile_id)
await engine.deactivate_if_active(automation_id)
# Build update kwargs — use sentinel for Optional[str] fields
update_kwargs = dict(
profile_id=profile_id,
automation_id=automation_id,
name=data.name,
enabled=data.enabled,
condition_logic=data.condition_logic,
@@ -236,34 +236,34 @@ async def update_profile(
if data.deactivation_scene_preset_id is not None:
update_kwargs["deactivation_scene_preset_id"] = data.deactivation_scene_preset_id
profile = store.update_profile(**update_kwargs)
automation = store.update_automation(**update_kwargs)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
# Re-evaluate immediately if profile is enabled (may have new conditions/scene)
if profile.enabled:
# Re-evaluate immediately if automation is enabled (may have new conditions/scene)
if automation.enabled:
await engine.trigger_evaluate()
return _profile_to_response(profile, engine)
return _automation_to_response(automation, engine)
@router.delete(
"/api/v1/profiles/{profile_id}",
"/api/v1/automations/{automation_id}",
status_code=204,
tags=["Profiles"],
tags=["Automations"],
)
async def delete_profile(
profile_id: str,
async def delete_automation(
automation_id: str,
_auth: AuthRequired,
store: ProfileStore = Depends(get_profile_store),
engine: ProfileEngine = Depends(get_profile_engine),
store: AutomationStore = Depends(get_automation_store),
engine: AutomationEngine = Depends(get_automation_engine),
):
"""Delete a profile."""
# Deactivate first (stop owned targets)
await engine.deactivate_if_active(profile_id)
"""Delete an automation."""
# Deactivate first
await engine.deactivate_if_active(automation_id)
try:
store.delete_profile(profile_id)
store.delete_automation(automation_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@@ -271,45 +271,45 @@ async def delete_profile(
# ===== Enable/Disable =====
@router.post(
"/api/v1/profiles/{profile_id}/enable",
response_model=ProfileResponse,
tags=["Profiles"],
"/api/v1/automations/{automation_id}/enable",
response_model=AutomationResponse,
tags=["Automations"],
)
async def enable_profile(
profile_id: str,
async def enable_automation(
automation_id: str,
_auth: AuthRequired,
store: ProfileStore = Depends(get_profile_store),
engine: ProfileEngine = Depends(get_profile_engine),
store: AutomationStore = Depends(get_automation_store),
engine: AutomationEngine = Depends(get_automation_engine),
):
"""Enable a profile."""
"""Enable an automation."""
try:
profile = store.update_profile(profile_id=profile_id, enabled=True)
automation = store.update_automation(automation_id=automation_id, enabled=True)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
# Evaluate immediately so targets start without waiting for the next poll cycle
# Evaluate immediately so scene activates without waiting for the next poll cycle
await engine.trigger_evaluate()
return _profile_to_response(profile, engine)
return _automation_to_response(automation, engine)
@router.post(
"/api/v1/profiles/{profile_id}/disable",
response_model=ProfileResponse,
tags=["Profiles"],
"/api/v1/automations/{automation_id}/disable",
response_model=AutomationResponse,
tags=["Automations"],
)
async def disable_profile(
profile_id: str,
async def disable_automation(
automation_id: str,
_auth: AuthRequired,
store: ProfileStore = Depends(get_profile_store),
engine: ProfileEngine = Depends(get_profile_engine),
store: AutomationStore = Depends(get_automation_store),
engine: AutomationEngine = Depends(get_automation_engine),
):
"""Disable a profile and stop any targets it owns."""
await engine.deactivate_if_active(profile_id)
"""Disable an automation and deactivate it."""
await engine.deactivate_if_active(automation_id)
try:
profile = store.update_profile(profile_id=profile_id, enabled=False)
automation = store.update_automation(automation_id=automation_id, enabled=False)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
return _profile_to_response(profile, engine)
return _automation_to_response(automation, engine)

View File

@@ -7,11 +7,11 @@ from fastapi import APIRouter, Depends, HTTPException
from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
get_automation_engine,
get_automation_store,
get_device_store,
get_picture_target_store,
get_processor_manager,
get_profile_engine,
get_profile_store,
get_scene_preset_store,
)
from wled_controller.api.schemas.scene_presets import (
@@ -28,10 +28,10 @@ from wled_controller.core.scenes.scene_activator import (
)
from wled_controller.storage import DeviceStore
from wled_controller.storage.picture_target_store import PictureTargetStore
from wled_controller.storage.profile_store import ProfileStore
from wled_controller.storage.automation_store import AutomationStore
from wled_controller.storage.scene_preset import ScenePreset
from wled_controller.storage.scene_preset_store import ScenePresetStore
from wled_controller.core.profiles.profile_engine import ProfileEngine
from wled_controller.core.automations.automation_engine import AutomationEngine
from wled_controller.utils import get_logger
logger = get_logger(__name__)
@@ -56,10 +56,10 @@ def _preset_to_response(preset: ScenePreset) -> ScenePresetResponse:
"device_id": d.device_id,
"software_brightness": d.software_brightness,
} for d in preset.devices],
profiles=[{
"profile_id": p.profile_id,
"enabled": p.enabled,
} for p in preset.profiles],
automations=[{
"automation_id": a.automation_id,
"enabled": a.enabled,
} for a in preset.automations],
order=preset.order,
created_at=preset.created_at,
updated_at=preset.updated_at,
@@ -80,12 +80,12 @@ async def create_scene_preset(
store: ScenePresetStore = Depends(get_scene_preset_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
device_store: DeviceStore = Depends(get_device_store),
profile_store: ProfileStore = Depends(get_profile_store),
automation_store: AutomationStore = Depends(get_automation_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Capture current state as a new scene preset."""
targets, devices, profiles = capture_current_snapshot(
target_store, device_store, profile_store, manager,
targets, devices, automations = capture_current_snapshot(
target_store, device_store, automation_store, manager,
)
now = datetime.utcnow()
@@ -96,7 +96,7 @@ async def create_scene_preset(
color=data.color,
targets=targets,
devices=devices,
profiles=profiles,
automations=automations,
order=store.count(),
created_at=now,
updated_at=now,
@@ -200,12 +200,12 @@ async def recapture_scene_preset(
store: ScenePresetStore = Depends(get_scene_preset_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
device_store: DeviceStore = Depends(get_device_store),
profile_store: ProfileStore = Depends(get_profile_store),
automation_store: AutomationStore = Depends(get_automation_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Re-capture current state into an existing preset (updates snapshot)."""
targets, devices, profiles = capture_current_snapshot(
target_store, device_store, profile_store, manager,
targets, devices, automations = capture_current_snapshot(
target_store, device_store, automation_store, manager,
)
new_snapshot = ScenePreset(
@@ -213,7 +213,7 @@ async def recapture_scene_preset(
name="",
targets=targets,
devices=devices,
profiles=profiles,
automations=automations,
)
try:
@@ -237,8 +237,8 @@ async def activate_scene_preset(
store: ScenePresetStore = Depends(get_scene_preset_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
device_store: DeviceStore = Depends(get_device_store),
profile_store: ProfileStore = Depends(get_profile_store),
engine: ProfileEngine = Depends(get_profile_engine),
automation_store: AutomationStore = Depends(get_automation_store),
engine: AutomationEngine = Depends(get_automation_engine),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Activate a scene preset — restore the captured state."""
@@ -248,7 +248,7 @@ async def activate_scene_preset(
raise HTTPException(status_code=404, detail=str(e))
status, errors = await apply_scene_state(
preset, target_store, device_store, profile_store, engine, manager,
preset, target_store, device_store, automation_store, engine, manager,
)
if not errors:

View File

@@ -182,9 +182,9 @@ async def get_displays(
async def get_running_processes(_: AuthRequired):
"""Get list of currently running process names.
Returns a sorted list of unique process names for use in profile conditions.
Returns a sorted list of unique process names for use in automation conditions.
"""
from wled_controller.core.profiles.platform_detector import PlatformDetector
from wled_controller.core.automations.platform_detector import PlatformDetector
try:
detector = PlatformDetector()
@@ -271,7 +271,7 @@ STORE_MAP = {
"audio_sources": "audio_sources_file",
"audio_templates": "audio_templates_file",
"value_sources": "value_sources_file",
"profiles": "profiles_file",
"automations": "automations_file",
"scene_presets": "scene_presets_file",
}

View File

@@ -1,4 +1,4 @@
"""Profile-related schemas."""
"""Automation-related schemas."""
from datetime import datetime
from typing import List, Optional
@@ -7,7 +7,7 @@ from pydantic import BaseModel, Field
class ConditionSchema(BaseModel):
"""A single condition within a profile."""
"""A single condition within an automation."""
condition_type: str = Field(description="Condition type discriminator (e.g. 'application')")
# Application condition fields
@@ -27,11 +27,11 @@ class ConditionSchema(BaseModel):
match_mode: Optional[str] = Field(None, description="'exact', 'contains', or 'regex' (for mqtt condition)")
class ProfileCreate(BaseModel):
"""Request to create a profile."""
class AutomationCreate(BaseModel):
"""Request to create an automation."""
name: str = Field(description="Profile name", min_length=1, max_length=100)
enabled: bool = Field(default=True, description="Whether the profile is enabled")
name: str = Field(description="Automation name", min_length=1, max_length=100)
enabled: bool = Field(default=True, description="Whether the automation is enabled")
condition_logic: str = Field(default="or", description="How conditions combine: 'or' or 'and'")
conditions: List[ConditionSchema] = Field(default_factory=list, description="List of conditions")
scene_preset_id: Optional[str] = Field(None, description="Scene preset to activate")
@@ -39,11 +39,11 @@ class ProfileCreate(BaseModel):
deactivation_scene_preset_id: Optional[str] = Field(None, description="Scene preset for fallback deactivation")
class ProfileUpdate(BaseModel):
"""Request to update a profile."""
class AutomationUpdate(BaseModel):
"""Request to update an automation."""
name: Optional[str] = Field(None, description="Profile name", min_length=1, max_length=100)
enabled: Optional[bool] = Field(None, description="Whether the profile is enabled")
name: Optional[str] = Field(None, description="Automation name", min_length=1, max_length=100)
enabled: Optional[bool] = Field(None, description="Whether the automation is enabled")
condition_logic: Optional[str] = Field(None, description="How conditions combine: 'or' or 'and'")
conditions: Optional[List[ConditionSchema]] = Field(None, description="List of conditions")
scene_preset_id: Optional[str] = Field(None, description="Scene preset to activate")
@@ -51,26 +51,26 @@ class ProfileUpdate(BaseModel):
deactivation_scene_preset_id: Optional[str] = Field(None, description="Scene preset for fallback deactivation")
class ProfileResponse(BaseModel):
"""Profile information response."""
class AutomationResponse(BaseModel):
"""Automation information response."""
id: str = Field(description="Profile ID")
name: str = Field(description="Profile name")
enabled: bool = Field(description="Whether the profile is enabled")
id: str = Field(description="Automation ID")
name: str = Field(description="Automation name")
enabled: bool = Field(description="Whether the automation is enabled")
condition_logic: str = Field(description="Condition combination logic")
conditions: List[ConditionSchema] = Field(description="List of conditions")
scene_preset_id: Optional[str] = Field(None, description="Scene preset to activate")
deactivation_mode: str = Field(default="none", description="Deactivation behavior")
deactivation_scene_preset_id: Optional[str] = Field(None, description="Fallback scene preset")
is_active: bool = Field(default=False, description="Whether the profile is currently active")
last_activated_at: Optional[datetime] = Field(None, description="Last time this profile was activated")
last_deactivated_at: Optional[datetime] = Field(None, description="Last time this profile was deactivated")
is_active: bool = Field(default=False, description="Whether the automation is currently active")
last_activated_at: Optional[datetime] = Field(None, description="Last time this automation was activated")
last_deactivated_at: Optional[datetime] = Field(None, description="Last time this automation was deactivated")
created_at: datetime = Field(description="Creation timestamp")
updated_at: datetime = Field(description="Last update timestamp")
class ProfileListResponse(BaseModel):
"""List of profiles response."""
class AutomationListResponse(BaseModel):
"""List of automations response."""
profiles: List[ProfileResponse] = Field(description="List of profiles")
count: int = Field(description="Number of profiles")
automations: List[AutomationResponse] = Field(description="List of automations")
count: int = Field(description="Number of automations")

View File

@@ -20,8 +20,8 @@ class DeviceBrightnessSnapshotSchema(BaseModel):
software_brightness: int = 255
class ProfileSnapshotSchema(BaseModel):
profile_id: str
class AutomationSnapshotSchema(BaseModel):
automation_id: str
enabled: bool = True
@@ -51,7 +51,7 @@ class ScenePresetResponse(BaseModel):
color: str
targets: List[TargetSnapshotSchema]
devices: List[DeviceBrightnessSnapshotSchema]
profiles: List[ProfileSnapshotSchema]
automations: List[AutomationSnapshotSchema]
order: int
created_at: datetime
updated_at: datetime