Add tags to all entity types with chip-based input and autocomplete
- Add `tags: List[str]` field to all 13 entity types (devices, output targets, CSS sources, picture sources, audio sources, value sources, sync clocks, automations, scene presets, capture/audio/PP/pattern templates) - Update all stores, schemas, and route handlers for tag CRUD - Add GET /api/v1/tags endpoint aggregating unique tags across all stores - Create TagInput component with chip display, autocomplete dropdown, keyboard navigation, and API-backed suggestions - Display tag chips on all entity cards (searchable via existing text filter) - Add tag input to all 14 editor modals with dirty check support - Add CSS styles and i18n keys (en/ru/zh) for tag UI - Also includes code review fixes: thread safety, perf, store dedup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@@ -18,7 +18,23 @@ from pydantic import BaseModel
|
||||
|
||||
from wled_controller import __version__
|
||||
from wled_controller.api.auth import AuthRequired
|
||||
from wled_controller.api.dependencies import get_auto_backup_engine, get_processor_manager
|
||||
from wled_controller.api.dependencies import (
|
||||
get_auto_backup_engine,
|
||||
get_audio_source_store,
|
||||
get_audio_template_store,
|
||||
get_automation_store,
|
||||
get_color_strip_store,
|
||||
get_device_store,
|
||||
get_output_target_store,
|
||||
get_pattern_template_store,
|
||||
get_picture_source_store,
|
||||
get_pp_template_store,
|
||||
get_processor_manager,
|
||||
get_scene_preset_store,
|
||||
get_sync_clock_store,
|
||||
get_template_store,
|
||||
get_value_source_store,
|
||||
)
|
||||
from wled_controller.api.schemas.system import (
|
||||
AutoBackupSettings,
|
||||
AutoBackupStatusResponse,
|
||||
@@ -104,7 +120,7 @@ async def health_check():
|
||||
|
||||
return HealthResponse(
|
||||
status="healthy",
|
||||
timestamp=datetime.utcnow(),
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
version=__version__,
|
||||
)
|
||||
|
||||
@@ -124,6 +140,39 @@ async def get_version():
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/v1/tags", tags=["Tags"])
|
||||
async def list_all_tags(_: AuthRequired):
|
||||
"""Get all tags used across all entities."""
|
||||
all_tags: set[str] = set()
|
||||
store_getters = [
|
||||
get_device_store, get_output_target_store, get_color_strip_store,
|
||||
get_picture_source_store, get_audio_source_store, get_value_source_store,
|
||||
get_sync_clock_store, get_automation_store, get_scene_preset_store,
|
||||
get_template_store, get_audio_template_store, get_pp_template_store,
|
||||
get_pattern_template_store,
|
||||
]
|
||||
for getter in store_getters:
|
||||
try:
|
||||
store = getter()
|
||||
except RuntimeError:
|
||||
continue
|
||||
# Each store has a different "get all" method name
|
||||
items = None
|
||||
for method_name in (
|
||||
"get_all_devices", "get_all_targets", "get_all_sources",
|
||||
"get_all_streams", "get_all_clocks", "get_all_automations",
|
||||
"get_all_presets", "get_all_templates",
|
||||
):
|
||||
fn = getattr(store, method_name, None)
|
||||
if fn is not None:
|
||||
items = fn()
|
||||
break
|
||||
if items:
|
||||
for item in items:
|
||||
all_tags.update(getattr(item, 'tags', []))
|
||||
return {"tags": sorted(all_tags)}
|
||||
|
||||
|
||||
@router.get("/api/v1/config/displays", response_model=DisplayListResponse, tags=["Config"])
|
||||
async def get_displays(
|
||||
_: AuthRequired,
|
||||
@@ -238,7 +287,7 @@ def get_system_performance(_: AuthRequired):
|
||||
ram_total_mb=round(mem.total / 1024 / 1024, 1),
|
||||
ram_percent=mem.percent,
|
||||
gpu=gpu,
|
||||
timestamp=datetime.utcnow(),
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
|
||||
|
||||
@@ -318,14 +367,14 @@ def backup_config(_: AuthRequired):
|
||||
"format": "ledgrab-backup",
|
||||
"format_version": 1,
|
||||
"app_version": __version__,
|
||||
"created_at": datetime.utcnow().isoformat() + "Z",
|
||||
"created_at": datetime.now(timezone.utc).isoformat() + "Z",
|
||||
"store_count": len(stores),
|
||||
},
|
||||
"stores": stores,
|
||||
}
|
||||
|
||||
content = json.dumps(backup, indent=2, ensure_ascii=False)
|
||||
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H%M%S")
|
||||
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H%M%S")
|
||||
filename = f"ledgrab-backup-{timestamp}.json"
|
||||
|
||||
return StreamingResponse(
|
||||
|
||||
Reference in New Issue
Block a user