Add CSPT entity, processed CSS source type, reverse filter, and UI improvements
- Add Color Strip Processing Template (CSPT) entity: reusable filter chains for 1D LED strip postprocessing (backend, storage, API, frontend CRUD) - Add "processed" color strip source type that wraps another CSS source and applies a CSPT filter chain (dataclass, stream, schema, modal, cards) - Add Reverse filter for strip LED order reversal - Add CSPT and processed CSS nodes/edges to visual graph editor - Add CSPT test preview WS endpoint with input source selection - Add device settings CSPT template selector (add + edit modals with hints) - Use icon grids for palette quantization preset selector in filter lists - Use EntitySelect for template references and test modal source selectors - Fix filters.css_filter_template.desc missing localization - Fix icon grid cell height inequality (grid-auto-rows: 1fr) - Rename "Processed" subtab to "Processing Templates" - Localize all new strings (en/ru/zh) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,19 +59,8 @@ logger = get_logger(__name__)
|
||||
# Prime psutil CPU counter (first call always returns 0.0)
|
||||
psutil.cpu_percent(interval=None)
|
||||
|
||||
# Try to initialize NVIDIA GPU monitoring
|
||||
_nvml_available = False
|
||||
try:
|
||||
import pynvml as _pynvml_mod # nvidia-ml-py (the pynvml wrapper is deprecated)
|
||||
|
||||
_pynvml_mod.nvmlInit()
|
||||
_nvml_handle = _pynvml_mod.nvmlDeviceGetHandleByIndex(0)
|
||||
_nvml_available = True
|
||||
_nvml = _pynvml_mod
|
||||
logger.info(f"NVIDIA GPU monitoring enabled: {_nvml.nvmlDeviceGetName(_nvml_handle)}")
|
||||
except Exception:
|
||||
_nvml = None
|
||||
logger.info("NVIDIA GPU monitoring unavailable (pynvml not installed or no NVIDIA GPU)")
|
||||
# GPU monitoring (initialized once in utils.gpu, shared with metrics_history)
|
||||
from wled_controller.utils.gpu import nvml_available as _nvml_available, nvml as _nvml, nvml_handle as _nvml_handle
|
||||
|
||||
|
||||
def _get_cpu_name() -> str | None:
|
||||
@@ -156,17 +145,9 @@ async def list_all_tags(_: AuthRequired):
|
||||
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
|
||||
# BaseJsonStore subclasses provide get_all(); DeviceStore provides get_all_devices()
|
||||
fn = getattr(store, "get_all", None) or getattr(store, "get_all_devices", None)
|
||||
items = fn() if fn else None
|
||||
if items:
|
||||
for item in items:
|
||||
all_tags.update(getattr(item, 'tags', []))
|
||||
@@ -191,9 +172,9 @@ async def get_displays(
|
||||
from wled_controller.core.capture_engines import EngineRegistry
|
||||
|
||||
engine_cls = EngineRegistry.get_engine(engine_type)
|
||||
display_dataclasses = engine_cls.get_available_displays()
|
||||
display_dataclasses = await asyncio.to_thread(engine_cls.get_available_displays)
|
||||
else:
|
||||
display_dataclasses = get_available_displays()
|
||||
display_dataclasses = await asyncio.to_thread(get_available_displays)
|
||||
|
||||
# Convert dataclass DisplayInfo to Pydantic DisplayInfo
|
||||
displays = [
|
||||
@@ -321,6 +302,7 @@ STORE_MAP = {
|
||||
"audio_templates": "audio_templates_file",
|
||||
"value_sources": "value_sources_file",
|
||||
"sync_clocks": "sync_clocks_file",
|
||||
"color_strip_processing_templates": "color_strip_processing_templates_file",
|
||||
"automations": "automations_file",
|
||||
"scene_presets": "scene_presets_file",
|
||||
}
|
||||
@@ -579,11 +561,13 @@ async def adb_connect(_: AuthRequired, request: AdbConnectRequest):
|
||||
adb = _get_adb_path()
|
||||
logger.info(f"Connecting ADB device: {address}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[adb, "connect", address],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
adb, "connect", address,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
output = (result.stdout + result.stderr).strip()
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=10)
|
||||
output = (stdout.decode() + stderr.decode()).strip()
|
||||
if "connected" in output.lower():
|
||||
return {"status": "connected", "address": address, "message": output}
|
||||
raise HTTPException(status_code=400, detail=output or "Connection failed")
|
||||
@@ -592,7 +576,7 @@ async def adb_connect(_: AuthRequired, request: AdbConnectRequest):
|
||||
status_code=500,
|
||||
detail="adb not found on PATH. Install Android SDK Platform-Tools.",
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
except asyncio.TimeoutError:
|
||||
raise HTTPException(status_code=504, detail="ADB connect timed out")
|
||||
|
||||
|
||||
@@ -606,12 +590,14 @@ async def adb_disconnect(_: AuthRequired, request: AdbConnectRequest):
|
||||
adb = _get_adb_path()
|
||||
logger.info(f"Disconnecting ADB device: {address}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[adb, "disconnect", address],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
adb, "disconnect", address,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
return {"status": "disconnected", "message": result.stdout.strip()}
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=10)
|
||||
return {"status": "disconnected", "message": stdout.decode().strip()}
|
||||
except FileNotFoundError:
|
||||
raise HTTPException(status_code=500, detail="adb not found on PATH")
|
||||
except subprocess.TimeoutExpired:
|
||||
except asyncio.TimeoutError:
|
||||
raise HTTPException(status_code=504, detail="ADB disconnect timed out")
|
||||
|
||||
Reference in New Issue
Block a user