feat: asset-based image/video sources, notification sounds, UI improvements
Some checks failed
Lint & Test / test (push) Has been cancelled
Some checks failed
Lint & Test / test (push) Has been cancelled
- Replace URL-based image_source/url fields with image_asset_id/video_asset_id on StaticImagePictureSource and VideoCaptureSource (clean break, no migration) - Resolve asset IDs to file paths at runtime via AssetStore.get_file_path() - Add EntitySelect asset pickers for image/video in stream editor modal - Add notification sound configuration (global sound + per-app overrides) - Unify per-app color and sound overrides into single "Per-App Overrides" section - Persist notification history between server restarts - Add asset management system (upload, edit, delete, soft-delete) - Replace emoji buttons with SVG icons throughout UI - Various backend improvements: SQLite stores, auth, backup, MQTT, webhooks
This commit is contained in:
@@ -163,8 +163,8 @@ async def create_target(
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create target: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
logger.error("Failed to create target: %s", e, exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
@router.get("/api/v1/output-targets", response_model=OutputTargetListResponse, tags=["Targets"])
|
||||
@@ -291,14 +291,16 @@ async def update_target(
|
||||
css_changed=data.color_strip_source_id is not None,
|
||||
brightness_vs_changed=(data.brightness_value_source_id is not None or kc_brightness_vs_changed),
|
||||
)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
logger.debug("Processor config update skipped for target %s: %s", target_id, e)
|
||||
pass
|
||||
|
||||
# Device change requires async stop -> swap -> start cycle
|
||||
if data.device_id is not None:
|
||||
try:
|
||||
await manager.update_target_device(target_id, target.device_id)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
logger.debug("Device update skipped for target %s: %s", target_id, e)
|
||||
pass
|
||||
|
||||
fire_entity_event("output_target", "updated", target_id)
|
||||
@@ -309,8 +311,8 @@ async def update_target(
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update target: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
logger.error("Failed to update target: %s", e, exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
@router.delete("/api/v1/output-targets/{target_id}", status_code=204, tags=["Targets"])
|
||||
@@ -325,13 +327,15 @@ async def delete_target(
|
||||
# Stop processing if running
|
||||
try:
|
||||
await manager.stop_processing(target_id)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
logger.debug("Stop processing skipped for target %s (not running): %s", target_id, e)
|
||||
pass
|
||||
|
||||
# Remove from manager
|
||||
try:
|
||||
manager.remove_target(target_id)
|
||||
except (ValueError, RuntimeError):
|
||||
except (ValueError, RuntimeError) as e:
|
||||
logger.debug("Remove target from manager skipped for %s: %s", target_id, e)
|
||||
pass
|
||||
|
||||
# Delete from store
|
||||
@@ -343,5 +347,5 @@ async def delete_target(
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete target: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
logger.error("Failed to delete target: %s", e, exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
Reference in New Issue
Block a user