fix(activity-log): final-review fixes - crosslink keys + sanitize parity
- _ENTITY_NAV map keys corrected to match backend entity_type (device, color_strip_source, audio_source data-id) + scene_playlist crosslink added - sanitize_display applied uniformly to owner-authored names at remaining record sites (dependencies entity_name, device_health, automation_engine, output_targets_control, scene_presets, scene_playlists)
This commit is contained in:
@@ -318,7 +318,7 @@ def fire_entity_event(
|
||||
severity=ActivitySeverity.INFO,
|
||||
entity_type=entity_type,
|
||||
entity_id=entity_id,
|
||||
entity_name=resolved_name,
|
||||
entity_name=sanitize_display(resolved_name) if resolved_name else None,
|
||||
message=message,
|
||||
)
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ from ledgrab.storage.color_strip_source import (
|
||||
from ledgrab.storage.picture_source_store import PictureSourceStore
|
||||
from ledgrab.storage.wled_output_target import WledOutputTarget
|
||||
from ledgrab.storage.output_target_store import OutputTargetStore
|
||||
from ledgrab.core.activity_log.sanitize import sanitize_display
|
||||
from ledgrab.utils import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -48,7 +49,7 @@ def _record_capture(action: str, target_id: str, target_name: str | None, messag
|
||||
severity=ActivitySeverity.INFO,
|
||||
entity_type="output_target",
|
||||
entity_id=target_id,
|
||||
entity_name=target_name,
|
||||
entity_name=sanitize_display(target_name) if target_name else None,
|
||||
message=message,
|
||||
)
|
||||
|
||||
@@ -75,11 +76,13 @@ async def bulk_start_processing(
|
||||
await manager.start_processing(target_id)
|
||||
started.append(target_id)
|
||||
logger.info(f"Bulk start: started processing for target {target_id}")
|
||||
_tgt_name_raw = getattr(_tgt, "name", None)
|
||||
_tgt_safe = sanitize_display(_tgt_name_raw) if _tgt_name_raw else None
|
||||
_record_capture(
|
||||
"capture.started",
|
||||
target_id,
|
||||
getattr(_tgt, "name", None),
|
||||
f"Capture started for target '{getattr(_tgt, 'name', target_id)}' (bulk)",
|
||||
_tgt_safe,
|
||||
f"Capture started for target '{_tgt_safe or target_id}' (bulk)",
|
||||
)
|
||||
except ValueError as e:
|
||||
errors[target_id] = str(e)
|
||||
@@ -119,11 +122,12 @@ async def bulk_stop_processing(
|
||||
_tgt_name = target_store.get_target(target_id).name
|
||||
except Exception:
|
||||
pass
|
||||
_tgt_name_safe = sanitize_display(_tgt_name) if _tgt_name else None
|
||||
_record_capture(
|
||||
"capture.stopped",
|
||||
target_id,
|
||||
_tgt_name,
|
||||
f"Capture stopped for target '{_tgt_name or target_id}' (bulk)",
|
||||
_tgt_name_safe,
|
||||
f"Capture stopped for target '{_tgt_name_safe or target_id}' (bulk)",
|
||||
)
|
||||
except ValueError as e:
|
||||
errors[target_id] = str(e)
|
||||
@@ -153,11 +157,13 @@ async def start_processing(
|
||||
await manager.start_processing(target_id)
|
||||
|
||||
logger.info(f"Started processing for target {target_id}")
|
||||
_tgt_name_raw2 = getattr(target, "name", None)
|
||||
_tgt_safe2 = sanitize_display(_tgt_name_raw2) if _tgt_name_raw2 else None
|
||||
_record_capture(
|
||||
"capture.started",
|
||||
target_id,
|
||||
getattr(target, "name", None),
|
||||
f"Capture started for target '{getattr(target, 'name', target_id)}'",
|
||||
_tgt_safe2,
|
||||
f"Capture started for target '{_tgt_safe2 or target_id}'",
|
||||
)
|
||||
return {"status": "started", "target_id": target_id}
|
||||
|
||||
@@ -192,11 +198,12 @@ async def stop_processing(
|
||||
_target_name = target_store.get_target(target_id).name
|
||||
except Exception:
|
||||
pass
|
||||
_target_name_safe = sanitize_display(_target_name) if _target_name else None
|
||||
_record_capture(
|
||||
"capture.stopped",
|
||||
target_id,
|
||||
_target_name,
|
||||
f"Capture stopped for target '{_target_name or target_id}'",
|
||||
_target_name_safe,
|
||||
f"Capture stopped for target '{_target_name_safe or target_id}'",
|
||||
)
|
||||
return {"status": "stopped", "target_id": target_id}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from ledgrab.api.auth import AuthRequired
|
||||
from ledgrab.core.activity_log.sanitize import sanitize_display
|
||||
from ledgrab.api.dependencies import (
|
||||
fire_entity_event,
|
||||
get_playlist_engine,
|
||||
@@ -272,14 +273,15 @@ async def start_scene_playlist(
|
||||
_pl_name = store.get_playlist(playlist_id).name
|
||||
except Exception:
|
||||
pass
|
||||
_safe_pl_name = sanitize_display(_pl_name) if _pl_name else None
|
||||
rec.record(
|
||||
category=ActivityCategory.CAPTURE,
|
||||
action="playlist.started",
|
||||
severity=ActivitySeverity.INFO,
|
||||
entity_type="scene_playlist",
|
||||
entity_id=playlist_id,
|
||||
entity_name=_pl_name,
|
||||
message=f"Playlist '{_pl_name or playlist_id}' started",
|
||||
entity_name=_safe_pl_name,
|
||||
message=f"Playlist '{_safe_pl_name or playlist_id}' started",
|
||||
)
|
||||
|
||||
return PlaylistRuntimeStateSchema(**engine.get_state())
|
||||
@@ -312,14 +314,15 @@ async def stop_scene_playlist(
|
||||
|
||||
rec = get_module_recorder()
|
||||
if rec is not None:
|
||||
_safe_stopped_name = sanitize_display(_stopped_name) if _stopped_name else None
|
||||
rec.record(
|
||||
category=ActivityCategory.CAPTURE,
|
||||
action="playlist.stopped",
|
||||
severity=ActivitySeverity.INFO,
|
||||
entity_type="scene_playlist",
|
||||
entity_id=stopped_id,
|
||||
entity_name=_stopped_name,
|
||||
message=f"Playlist '{_stopped_name or stopped_id}' stopped",
|
||||
entity_name=_safe_stopped_name,
|
||||
message=f"Playlist '{_safe_stopped_name or stopped_id}' stopped",
|
||||
)
|
||||
|
||||
return PlaylistRuntimeStateSchema(**engine.get_state())
|
||||
|
||||
@@ -6,6 +6,7 @@ from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from ledgrab.api.auth import AuthRequired
|
||||
from ledgrab.core.activity_log.sanitize import sanitize_display
|
||||
from ledgrab.api.dependencies import (
|
||||
fire_entity_event,
|
||||
get_output_target_store,
|
||||
@@ -294,14 +295,15 @@ async def activate_scene_preset(
|
||||
|
||||
rec = get_module_recorder()
|
||||
if rec is not None:
|
||||
_safe_preset_name = sanitize_display(preset.name) if preset.name else None
|
||||
rec.record(
|
||||
category=ActivityCategory.CAPTURE,
|
||||
action="scene.activated",
|
||||
severity=ActivitySeverity.INFO,
|
||||
entity_type="scene_preset",
|
||||
entity_id=preset_id,
|
||||
entity_name=preset.name,
|
||||
message=f"Scene preset '{preset.name}' activated",
|
||||
entity_name=_safe_preset_name,
|
||||
message=f"Scene preset '{_safe_preset_name or preset_id}' activated",
|
||||
)
|
||||
|
||||
return ActivateResponse(status=status, errors=errors)
|
||||
|
||||
@@ -729,10 +729,12 @@ class AutomationEngine:
|
||||
# Audit record — best-effort.
|
||||
try:
|
||||
from ledgrab.core.activity_log.recorder import get_module_recorder
|
||||
from ledgrab.core.activity_log.sanitize import sanitize_display
|
||||
from ledgrab.storage.activity_log import ActivityCategory, ActivitySeverity
|
||||
|
||||
rec = get_module_recorder()
|
||||
if rec is not None:
|
||||
_safe_name = sanitize_display(automation.name) if automation.name else None
|
||||
rec.record(
|
||||
category=ActivityCategory.CAPTURE,
|
||||
action="automation.activated",
|
||||
@@ -740,8 +742,8 @@ class AutomationEngine:
|
||||
actor="system",
|
||||
entity_type="automation",
|
||||
entity_id=automation.id,
|
||||
entity_name=automation.name,
|
||||
message=f"Automation '{automation.name}' activated",
|
||||
entity_name=_safe_name,
|
||||
message=f"Automation '{_safe_name or automation.id}' activated",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -774,6 +776,7 @@ class AutomationEngine:
|
||||
# Audit record — best-effort.
|
||||
try:
|
||||
from ledgrab.core.activity_log.recorder import get_module_recorder
|
||||
from ledgrab.core.activity_log.sanitize import sanitize_display
|
||||
from ledgrab.storage.activity_log import ActivityCategory, ActivitySeverity
|
||||
|
||||
rec = get_module_recorder()
|
||||
@@ -783,6 +786,7 @@ class AutomationEngine:
|
||||
_auto_name = self._store.get_automation(automation_id).name
|
||||
except Exception:
|
||||
pass
|
||||
_safe_deact_name = sanitize_display(_auto_name) if _auto_name else None
|
||||
rec.record(
|
||||
category=ActivityCategory.CAPTURE,
|
||||
action="automation.deactivated",
|
||||
@@ -790,8 +794,8 @@ class AutomationEngine:
|
||||
actor="system",
|
||||
entity_type="automation",
|
||||
entity_id=automation_id,
|
||||
entity_name=_auto_name,
|
||||
message=f"Automation '{_auto_name or automation_id}' deactivated",
|
||||
entity_name=_safe_deact_name,
|
||||
message=f"Automation '{_safe_deact_name or automation_id}' deactivated",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -11,6 +11,7 @@ from ledgrab.core.devices.led_client import (
|
||||
check_device_health,
|
||||
get_device_capabilities,
|
||||
)
|
||||
from ledgrab.core.activity_log.sanitize import sanitize_display
|
||||
from ledgrab.storage.activity_log import ActivityCategory, ActivitySeverity
|
||||
from ledgrab.utils import get_logger
|
||||
|
||||
@@ -142,7 +143,8 @@ class DeviceHealthMixin:
|
||||
device_name = self._device_store.get_device(device_id).name
|
||||
except Exception:
|
||||
pass
|
||||
display = device_name or device_id
|
||||
safe_name = sanitize_display(device_name) if device_name else None
|
||||
display = safe_name or device_id
|
||||
action = "device.online" if is_online else "device.offline"
|
||||
severity = ActivitySeverity.INFO if is_online else ActivitySeverity.WARNING
|
||||
status_word = "came online" if is_online else "went offline"
|
||||
@@ -153,7 +155,7 @@ class DeviceHealthMixin:
|
||||
actor="system",
|
||||
entity_type="device",
|
||||
entity_id=device_id,
|
||||
entity_name=device_name,
|
||||
entity_name=safe_name,
|
||||
message=f"Device '{display}' {status_word}",
|
||||
metadata={"latency_ms": state.health.latency_ms},
|
||||
)
|
||||
|
||||
@@ -88,13 +88,14 @@ const _filters: ActiveFilters = {
|
||||
// ─── Category → navigation target map (entity crosslinks) ──
|
||||
|
||||
const _ENTITY_NAV: Record<string, { tab: string; subTab: string | null; attr: string } | null> = {
|
||||
output_target: { tab: 'targets', subTab: 'led-targets', attr: 'data-target-id' },
|
||||
led_device: { tab: 'targets', subTab: 'led-devices', attr: 'data-device-id' },
|
||||
picture_source: { tab: 'streams', subTab: 'raw', attr: 'data-stream-id' },
|
||||
color_strip: { tab: 'streams', subTab: 'color_strip', attr: 'data-strip-id' },
|
||||
audio_source: { tab: 'streams', subTab: 'audio_capture', attr: 'data-audio-source-id' },
|
||||
output_target: { tab: 'targets', subTab: 'led-targets', attr: 'data-target-id' },
|
||||
device: { tab: 'targets', subTab: 'led-devices', attr: 'data-device-id' },
|
||||
picture_source: { tab: 'streams', subTab: 'raw', attr: 'data-stream-id' },
|
||||
color_strip_source: { tab: 'streams', subTab: 'color_strip', attr: 'data-css-id' },
|
||||
audio_source: { tab: 'streams', subTab: 'audio_capture', attr: 'data-id' },
|
||||
automation: { tab: 'automations', subTab: 'automations', attr: 'data-automation-id' },
|
||||
scene_preset: { tab: 'automations', subTab: 'scenes', attr: 'data-scene-id' },
|
||||
scene_playlist: { tab: 'automations', subTab: 'playlists', attr: 'data-playlist-id' },
|
||||
};
|
||||
|
||||
// ─── Severity icon helper ────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user