Comprehensive WebUI review: 41 UX/feature/CSS improvements

Safety & Correctness:
- Add confirmation dialogs to Stop All, turnOffDevice
- i18n confirm dialog (title, yes, no buttons)
- Fix duplicate tutorial-overlay ID
- Define missing CSS variables (--radius, --text-primary, --hover-bg, --input-bg)
- Fix toast z-index conflict with confirm dialog (2500 → 3000)

UX Consistency:
- Add backdrop-close to test modals
- Add device clone feature (only entity without it)
- Add sync clocks to command palette
- Replace 20+ hardcoded accent colors with CSS vars/color-mix()
- Remove dead .badge duplicate from components.css
- Make calibration elements keyboard-accessible (div → button)
- Add aria-labels to color picker swatches
- Fix pattern canvas mobile horizontal scroll
- Fix graph editor mobile bottom clipping

Polish:
- Add empty-state messages to all CardSection instances
- Convert 21 px font-sizes to rem
- Add scroll-behavior: smooth with reduced-motion override
- Add @media print styles
- Add :focus-visible to 4 missing interactive elements
- Fix settings modal close label (Cancel → Close)
- Fix api-key submit button i18n

New Features:
- Command palette actions: start/stop targets, activate scenes, enable/disable
- Bulk start/stop API endpoints (POST /output-targets/bulk/start|stop)
- OS notification history viewer modal
- Scene "used by" automation reference count on cards
- Clock elapsed time display on Streams tab cards
- Device "last seen" relative timestamp on cards
- Audio device refresh button in edit modal
- Composite layer drag-to-reorder
- MQTT settings panel (broker config with JSON persistence)
- WebSocket log viewer with level filtering and ring buffer
- Runtime log-level adjustment (GET/PUT endpoints + settings UI)
- Animated value source waveform canvas preview
- Gradient custom preset save/delete (localStorage)
- API key read-only display in settings
- Backup metadata (file size, auto/manual badges)
- Server restart button with confirm + overlay
- Partial config export/import per entity type
- Progressive disclosure in target editor (Advanced section)

CSS Architecture:
- Define radius scale tokens (--radius-sm/md/lg/pill)
- Scope .cs-filter selectors to remove 7 !important overrides
- Consolidate duplicate toggle switch (filter-list → settings-toggle)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 18:46:38 +03:00
parent a4a0e39b9b
commit 304fa24389
47 changed files with 2594 additions and 250 deletions

View File

@@ -22,6 +22,8 @@ from wled_controller.api.dependencies import (
get_template_store,
)
from wled_controller.api.schemas.output_targets import (
BulkTargetRequest,
BulkTargetResponse,
ExtractedColorResponse,
KCTestRectangleResponse,
KCTestResponse,
@@ -373,6 +375,64 @@ async def delete_target(
raise HTTPException(status_code=500, detail=str(e))
# ===== BULK PROCESSING CONTROL ENDPOINTS =====
@router.post("/api/v1/output-targets/bulk/start", response_model=BulkTargetResponse, tags=["Processing"])
async def bulk_start_processing(
body: BulkTargetRequest,
_auth: AuthRequired,
target_store: OutputTargetStore = Depends(get_output_target_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Start processing for multiple output targets. Returns lists of started IDs and per-ID errors."""
started: list[str] = []
errors: dict[str, str] = {}
for target_id in body.ids:
try:
target_store.get_target(target_id)
await manager.start_processing(target_id)
started.append(target_id)
logger.info(f"Bulk start: started processing for target {target_id}")
except ValueError as e:
errors[target_id] = str(e)
except RuntimeError as e:
msg = str(e)
for t in target_store.get_all_targets():
if t.id in msg:
msg = msg.replace(t.id, f"'{t.name}'")
errors[target_id] = msg
except Exception as e:
logger.error(f"Bulk start: failed to start target {target_id}: {e}")
errors[target_id] = str(e)
return BulkTargetResponse(started=started, errors=errors)
@router.post("/api/v1/output-targets/bulk/stop", response_model=BulkTargetResponse, tags=["Processing"])
async def bulk_stop_processing(
body: BulkTargetRequest,
_auth: AuthRequired,
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Stop processing for multiple output targets. Returns lists of stopped IDs and per-ID errors."""
stopped: list[str] = []
errors: dict[str, str] = {}
for target_id in body.ids:
try:
await manager.stop_processing(target_id)
stopped.append(target_id)
logger.info(f"Bulk stop: stopped processing for target {target_id}")
except ValueError as e:
errors[target_id] = str(e)
except Exception as e:
logger.error(f"Bulk stop: failed to stop target {target_id}: {e}")
errors[target_id] = str(e)
return BulkTargetResponse(stopped=stopped, errors=errors)
# ===== PROCESSING CONTROL ENDPOINTS =====
@router.post("/api/v1/output-targets/{target_id}/start", tags=["Processing"])